1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 
3 // Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland.
4 
5 // Copyright (c) 2015-2020, Oracle and/or its affiliates.
6 
7 // Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
8 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
9 // Contributed and/or modified by Adeel Ahmad, as part of Google Summer of Code 2018 program
10 
11 // Licensed under the Boost Software License version 1.0.
12 // http://www.boost.org/users/license.html
13 
14 #ifndef BOOST_GEOMETRY_UTIL_NORMALIZE_SPHEROIDAL_COORDINATES_HPP
15 #define BOOST_GEOMETRY_UTIL_NORMALIZE_SPHEROIDAL_COORDINATES_HPP
16 
17 #include <boost/geometry/core/assert.hpp>
18 #include <boost/geometry/core/cs.hpp>
19 #include <boost/geometry/util/math.hpp>
20 
21 
22 namespace boost { namespace geometry
23 {
24 
25 namespace math
26 {
27 
28 #ifndef DOXYGEN_NO_DETAIL
29 namespace detail
30 {
31 
32 // CoordinateType, radian, true
33 template <typename CoordinateType, typename Units, bool IsEquatorial = true>
34 struct constants_on_spheroid
35 {
periodboost::geometry::math::detail::constants_on_spheroid36     static inline CoordinateType period()
37     {
38         return math::two_pi<CoordinateType>();
39     }
40 
half_periodboost::geometry::math::detail::constants_on_spheroid41     static inline CoordinateType half_period()
42     {
43         return math::pi<CoordinateType>();
44     }
45 
quarter_periodboost::geometry::math::detail::constants_on_spheroid46     static inline CoordinateType quarter_period()
47     {
48         static CoordinateType const
49             pi_half = math::pi<CoordinateType>() / CoordinateType(2);
50         return pi_half;
51     }
52 
min_longitudeboost::geometry::math::detail::constants_on_spheroid53     static inline CoordinateType min_longitude()
54     {
55         static CoordinateType const minus_pi = -math::pi<CoordinateType>();
56         return minus_pi;
57     }
58 
max_longitudeboost::geometry::math::detail::constants_on_spheroid59     static inline CoordinateType max_longitude()
60     {
61         return math::pi<CoordinateType>();
62     }
63 
min_latitudeboost::geometry::math::detail::constants_on_spheroid64     static inline CoordinateType min_latitude()
65     {
66         static CoordinateType const minus_half_pi
67             = -math::half_pi<CoordinateType>();
68         return minus_half_pi;
69     }
70 
max_latitudeboost::geometry::math::detail::constants_on_spheroid71     static inline CoordinateType max_latitude()
72     {
73         return math::half_pi<CoordinateType>();
74     }
75 };
76 
77 template <typename CoordinateType>
78 struct constants_on_spheroid<CoordinateType, radian, false>
79     : constants_on_spheroid<CoordinateType, radian, true>
80 {
min_latitudeboost::geometry::math::detail::constants_on_spheroid81     static inline CoordinateType min_latitude()
82     {
83         return CoordinateType(0);
84     }
85 
max_latitudeboost::geometry::math::detail::constants_on_spheroid86     static inline CoordinateType max_latitude()
87     {
88         return math::pi<CoordinateType>();
89     }
90 };
91 
92 template <typename CoordinateType>
93 struct constants_on_spheroid<CoordinateType, degree, true>
94 {
periodboost::geometry::math::detail::constants_on_spheroid95     static inline CoordinateType period()
96     {
97         return CoordinateType(360.0);
98     }
99 
half_periodboost::geometry::math::detail::constants_on_spheroid100     static inline CoordinateType half_period()
101     {
102         return CoordinateType(180.0);
103     }
104 
quarter_periodboost::geometry::math::detail::constants_on_spheroid105     static inline CoordinateType quarter_period()
106     {
107         return CoordinateType(90.0);
108     }
109 
min_longitudeboost::geometry::math::detail::constants_on_spheroid110     static inline CoordinateType min_longitude()
111     {
112         return CoordinateType(-180.0);
113     }
114 
max_longitudeboost::geometry::math::detail::constants_on_spheroid115     static inline CoordinateType max_longitude()
116     {
117         return CoordinateType(180.0);
118     }
119 
min_latitudeboost::geometry::math::detail::constants_on_spheroid120     static inline CoordinateType min_latitude()
121     {
122         return CoordinateType(-90.0);
123     }
124 
max_latitudeboost::geometry::math::detail::constants_on_spheroid125     static inline CoordinateType max_latitude()
126     {
127         return CoordinateType(90.0);
128     }
129 };
130 
131 template <typename CoordinateType>
132 struct constants_on_spheroid<CoordinateType, degree, false>
133     : constants_on_spheroid<CoordinateType, degree, true>
134 {
min_latitudeboost::geometry::math::detail::constants_on_spheroid135     static inline CoordinateType min_latitude()
136     {
137         return CoordinateType(0);
138     }
139 
max_latitudeboost::geometry::math::detail::constants_on_spheroid140     static inline CoordinateType max_latitude()
141     {
142         return CoordinateType(180.0);
143     }
144 };
145 
146 
147 } // namespace detail
148 #endif // DOXYGEN_NO_DETAIL
149 
150 
151 template <typename Units, typename CoordinateType>
latitude_convert_ep(CoordinateType const & lat)152 inline CoordinateType latitude_convert_ep(CoordinateType const& lat)
153 {
154     typedef math::detail::constants_on_spheroid
155             <
156                 CoordinateType,
157                 Units
158             > constants;
159 
160     return constants::quarter_period() - lat;
161 }
162 
163 
164 template <typename Units, bool IsEquatorial, typename T>
is_latitude_pole(T const & lat)165 static bool is_latitude_pole(T const& lat)
166 {
167     typedef math::detail::constants_on_spheroid
168         <
169             T,
170             Units
171         > constants;
172 
173     return math::equals(math::abs(IsEquatorial
174                                     ? lat
175                                     : math::latitude_convert_ep<Units>(lat)),
176                         constants::quarter_period());
177 
178 }
179 
180 
181 template <typename Units, typename T>
is_longitude_antimeridian(T const & lon)182 static bool is_longitude_antimeridian(T const& lon)
183 {
184     typedef math::detail::constants_on_spheroid
185         <
186             T,
187             Units
188         > constants;
189 
190     return math::equals(math::abs(lon), constants::half_period());
191 
192 }
193 
194 
195 #ifndef DOXYGEN_NO_DETAIL
196 namespace detail
197 {
198 
199 
200 template <typename Units, bool IsEquatorial>
201 struct latitude_convert_if_polar
202 {
203     template <typename T>
applyboost::geometry::math::detail::latitude_convert_if_polar204     static inline void apply(T & /*lat*/) {}
205 };
206 
207 template <typename Units>
208 struct latitude_convert_if_polar<Units, false>
209 {
210     template <typename T>
applyboost::geometry::math::detail::latitude_convert_if_polar211     static inline void apply(T & lat)
212     {
213         lat = latitude_convert_ep<Units>(lat);
214     }
215 };
216 
217 
218 template <typename Units, typename CoordinateType, bool IsEquatorial = true>
219 class normalize_spheroidal_coordinates
220 {
221     typedef constants_on_spheroid<CoordinateType, Units> constants;
222 
223 protected:
normalize_up(CoordinateType const & value)224     static inline CoordinateType normalize_up(CoordinateType const& value)
225     {
226         return
227             math::mod(value + constants::half_period(), constants::period())
228             - constants::half_period();
229     }
230 
normalize_down(CoordinateType const & value)231     static inline CoordinateType normalize_down(CoordinateType const& value)
232     {
233         return
234             math::mod(value - constants::half_period(), constants::period())
235             + constants::half_period();
236     }
237 
238 public:
apply(CoordinateType & longitude)239     static inline void apply(CoordinateType& longitude)
240     {
241         // normalize longitude
242         if (math::equals(math::abs(longitude), constants::half_period()))
243         {
244             longitude = constants::half_period();
245         }
246         else if (longitude > constants::half_period())
247         {
248             longitude = normalize_up(longitude);
249             if (math::equals(longitude, -constants::half_period()))
250             {
251                 longitude = constants::half_period();
252             }
253         }
254         else if (longitude < -constants::half_period())
255         {
256             longitude = normalize_down(longitude);
257         }
258     }
259 
apply(CoordinateType & longitude,CoordinateType & latitude,bool normalize_poles=true)260     static inline void apply(CoordinateType& longitude,
261                              CoordinateType& latitude,
262                              bool normalize_poles = true)
263     {
264         latitude_convert_if_polar<Units, IsEquatorial>::apply(latitude);
265 
266 #ifdef BOOST_GEOMETRY_NORMALIZE_LATITUDE
267         // normalize latitude
268         if (math::larger(latitude, constants::half_period()))
269         {
270             latitude = normalize_up(latitude);
271         }
272         else if (math::smaller(latitude, -constants::half_period()))
273         {
274             latitude = normalize_down(latitude);
275         }
276 
277         // fix latitude range
278         if (latitude < constants::min_latitude())
279         {
280             latitude = -constants::half_period() - latitude;
281             longitude -= constants::half_period();
282         }
283         else if (latitude > constants::max_latitude())
284         {
285             latitude = constants::half_period() - latitude;
286             longitude -= constants::half_period();
287         }
288 #endif // BOOST_GEOMETRY_NORMALIZE_LATITUDE
289 
290         // normalize longitude
291         apply(longitude);
292 
293         // finally normalize poles
294         if (normalize_poles)
295         {
296             if (math::equals(math::abs(latitude), constants::max_latitude()))
297             {
298                 // for the north and south pole we set the longitude to 0
299                 // (works for both radians and degrees)
300                 longitude = CoordinateType(0);
301             }
302         }
303 
304         latitude_convert_if_polar<Units, IsEquatorial>::apply(latitude);
305 
306 #ifdef BOOST_GEOMETRY_NORMALIZE_LATITUDE
307         BOOST_GEOMETRY_ASSERT(! math::larger(constants::min_latitude(), latitude));
308         BOOST_GEOMETRY_ASSERT(! math::larger(latitude, constants::max_latitude()));
309 #endif // BOOST_GEOMETRY_NORMALIZE_LATITUDE
310 
311         BOOST_GEOMETRY_ASSERT(math::smaller(constants::min_longitude(), longitude));
312         BOOST_GEOMETRY_ASSERT(! math::larger(longitude, constants::max_longitude()));
313     }
314 };
315 
316 
317 template <typename Units, typename CoordinateType>
normalize_angle_loop(CoordinateType & angle)318 inline void normalize_angle_loop(CoordinateType& angle)
319 {
320     typedef constants_on_spheroid<CoordinateType, Units> constants;
321     CoordinateType const pi = constants::half_period();
322     CoordinateType const two_pi = constants::period();
323     while (angle > pi)
324         angle -= two_pi;
325     while (angle <= -pi)
326         angle += two_pi;
327 }
328 
329 template <typename Units, typename CoordinateType>
normalize_angle_cond(CoordinateType & angle)330 inline void normalize_angle_cond(CoordinateType& angle)
331 {
332     typedef constants_on_spheroid<CoordinateType, Units> constants;
333     CoordinateType const pi = constants::half_period();
334     CoordinateType const two_pi = constants::period();
335     if (angle > pi)
336         angle -= two_pi;
337     else if (angle <= -pi)
338         angle += two_pi;
339 }
340 
341 
342 } // namespace detail
343 #endif // DOXYGEN_NO_DETAIL
344 
345 
346 /*!
347 \brief Short utility to normalize the coordinates on a spheroid
348 \tparam Units The units of the coordindate system in the spheroid
349 \tparam CoordinateType The type of the coordinates
350 \param longitude Longitude
351 \param latitude Latitude
352 \ingroup utility
353 */
354 template <typename Units, typename CoordinateType>
normalize_spheroidal_coordinates(CoordinateType & longitude,CoordinateType & latitude)355 inline void normalize_spheroidal_coordinates(CoordinateType& longitude,
356                                              CoordinateType& latitude)
357 {
358     detail::normalize_spheroidal_coordinates
359         <
360             Units, CoordinateType
361         >::apply(longitude, latitude);
362 }
363 
364 template <typename Units, bool IsEquatorial, typename CoordinateType>
normalize_spheroidal_coordinates(CoordinateType & longitude,CoordinateType & latitude)365 inline void normalize_spheroidal_coordinates(CoordinateType& longitude,
366                                              CoordinateType& latitude)
367 {
368     detail::normalize_spheroidal_coordinates
369         <
370             Units, CoordinateType, IsEquatorial
371         >::apply(longitude, latitude);
372 }
373 
374 /*!
375 \brief Short utility to normalize the longitude on a spheroid.
376        Note that in general both coordinates should be normalized at once.
377        This utility is suitable e.g. for normalization of the difference of longitudes.
378 \tparam Units The units of the coordindate system in the spheroid
379 \tparam CoordinateType The type of the coordinates
380 \param longitude Longitude
381 \ingroup utility
382 */
383 template <typename Units, typename CoordinateType>
normalize_longitude(CoordinateType & longitude)384 inline void normalize_longitude(CoordinateType& longitude)
385 {
386     detail::normalize_spheroidal_coordinates
387         <
388             Units, CoordinateType
389         >::apply(longitude);
390 }
391 
392 /*!
393 \brief Short utility to normalize the azimuth on a spheroid
394        in the range (-180, 180].
395 \tparam Units The units of the coordindate system in the spheroid
396 \tparam CoordinateType The type of the coordinates
397 \param angle Angle
398 \ingroup utility
399 */
400 template <typename Units, typename CoordinateType>
normalize_azimuth(CoordinateType & angle)401 inline void normalize_azimuth(CoordinateType& angle)
402 {
403     normalize_longitude<Units, CoordinateType>(angle);
404 }
405 
406 /*!
407 \brief Normalize the given values.
408 \tparam ValueType The type of the values
409 \param x Value x
410 \param y Value y
411 TODO: adl1995 - Merge this function with
412 formulas/vertex_longitude.hpp
413 */
414 template<typename ValueType>
normalize_unit_vector(ValueType & x,ValueType & y)415 inline void normalize_unit_vector(ValueType& x, ValueType& y)
416 {
417     ValueType h = boost::math::hypot(x, y);
418 
419     BOOST_GEOMETRY_ASSERT(h > 0);
420 
421     x /= h; y /= h;
422 }
423 
424 /*!
425 \brief Short utility to calculate difference between two longitudes
426        normalized in range (-180, 180].
427 \tparam Units The units of the coordindate system in the spheroid
428 \tparam CoordinateType The type of the coordinates
429 \param longitude1 Longitude 1
430 \param longitude2 Longitude 2
431 \ingroup utility
432 */
433 template <typename Units, typename CoordinateType>
longitude_distance_signed(CoordinateType const & longitude1,CoordinateType const & longitude2)434 inline CoordinateType longitude_distance_signed(CoordinateType const& longitude1,
435                                                 CoordinateType const& longitude2)
436 {
437     CoordinateType diff = longitude2 - longitude1;
438     math::normalize_longitude<Units, CoordinateType>(diff);
439     return diff;
440 }
441 
442 
443 /*!
444 \brief Short utility to calculate difference between two longitudes
445        normalized in range [0, 360).
446 \tparam Units The units of the coordindate system in the spheroid
447 \tparam CoordinateType The type of the coordinates
448 \param longitude1 Longitude 1
449 \param longitude2 Longitude 2
450 \ingroup utility
451 */
452 template <typename Units, typename CoordinateType>
longitude_distance_unsigned(CoordinateType const & longitude1,CoordinateType const & longitude2)453 inline CoordinateType longitude_distance_unsigned(CoordinateType const& longitude1,
454                                                   CoordinateType const& longitude2)
455 {
456     typedef math::detail::constants_on_spheroid
457         <
458             CoordinateType, Units
459         > constants;
460 
461     CoordinateType const c0 = 0;
462     CoordinateType diff = longitude_distance_signed<Units>(longitude1, longitude2);
463     if (diff < c0) // (-180, 180] -> [0, 360)
464     {
465         diff += constants::period();
466     }
467     return diff;
468 }
469 
470 /*!
471 \brief The abs difference between longitudes in range [0, 180].
472 \tparam Units The units of the coordindate system in the spheroid
473 \tparam CoordinateType The type of the coordinates
474 \param longitude1 Longitude 1
475 \param longitude2 Longitude 2
476 \ingroup utility
477 */
478 template <typename Units, typename CoordinateType>
longitude_difference(CoordinateType const & longitude1,CoordinateType const & longitude2)479 inline CoordinateType longitude_difference(CoordinateType const& longitude1,
480                                            CoordinateType const& longitude2)
481 {
482     return math::abs(math::longitude_distance_signed<Units>(longitude1, longitude2));
483 }
484 
485 template <typename Units, typename CoordinateType>
longitude_interval_distance_signed(CoordinateType const & longitude_a1,CoordinateType const & longitude_a2,CoordinateType const & longitude_b)486 inline CoordinateType longitude_interval_distance_signed(CoordinateType const& longitude_a1,
487                                                          CoordinateType const& longitude_a2,
488                                                          CoordinateType const& longitude_b)
489 {
490     CoordinateType const c0 = 0;
491     CoordinateType dist_a12 = longitude_distance_signed<Units>(longitude_a1, longitude_a2);
492     CoordinateType dist_a1b = longitude_distance_signed<Units>(longitude_a1, longitude_b);
493     if (dist_a12 < c0)
494     {
495         dist_a12 = -dist_a12;
496         dist_a1b = -dist_a1b;
497     }
498 
499     return dist_a1b < c0 ? dist_a1b
500          : dist_a1b > dist_a12 ? dist_a1b - dist_a12
501          : c0;
502 }
503 
504 
505 } // namespace math
506 
507 
508 }} // namespace boost::geometry
509 
510 #endif // BOOST_GEOMETRY_UTIL_NORMALIZE_SPHEROIDAL_COORDINATES_HPP
511