1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 
3 // Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland.
4 
5 // Copyright (c) 2015-2019, 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 } // namespace detail
318 #endif // DOXYGEN_NO_DETAIL
319 
320 
321 /*!
322 \brief Short utility to normalize the coordinates on a spheroid
323 \tparam Units The units of the coordindate system in the spheroid
324 \tparam CoordinateType The type of the coordinates
325 \param longitude Longitude
326 \param latitude Latitude
327 \ingroup utility
328 */
329 template <typename Units, typename CoordinateType>
normalize_spheroidal_coordinates(CoordinateType & longitude,CoordinateType & latitude)330 inline void normalize_spheroidal_coordinates(CoordinateType& longitude,
331                                              CoordinateType& latitude)
332 {
333     detail::normalize_spheroidal_coordinates
334         <
335             Units, CoordinateType
336         >::apply(longitude, latitude);
337 }
338 
339 template <typename Units, bool IsEquatorial, typename CoordinateType>
normalize_spheroidal_coordinates(CoordinateType & longitude,CoordinateType & latitude)340 inline void normalize_spheroidal_coordinates(CoordinateType& longitude,
341                                              CoordinateType& latitude)
342 {
343     detail::normalize_spheroidal_coordinates
344         <
345             Units, CoordinateType, IsEquatorial
346         >::apply(longitude, latitude);
347 }
348 
349 /*!
350 \brief Short utility to normalize the longitude on a spheroid.
351        Note that in general both coordinates should be normalized at once.
352        This utility is suitable e.g. for normalization of the difference of longitudes.
353 \tparam Units The units of the coordindate system in the spheroid
354 \tparam CoordinateType The type of the coordinates
355 \param longitude Longitude
356 \ingroup utility
357 */
358 template <typename Units, typename CoordinateType>
normalize_longitude(CoordinateType & longitude)359 inline void normalize_longitude(CoordinateType& longitude)
360 {
361     detail::normalize_spheroidal_coordinates
362         <
363             Units, CoordinateType
364         >::apply(longitude);
365 }
366 
367 /*!
368 \brief Short utility to normalize the azimuth on a spheroid
369        in the range (-180, 180].
370 \tparam Units The units of the coordindate system in the spheroid
371 \tparam CoordinateType The type of the coordinates
372 \param angle Angle
373 \ingroup utility
374 */
375 template <typename Units, typename CoordinateType>
normalize_azimuth(CoordinateType & angle)376 inline void normalize_azimuth(CoordinateType& angle)
377 {
378     normalize_longitude<Units, CoordinateType>(angle);
379 }
380 
381 /*!
382 \brief Normalize the given values.
383 \tparam ValueType The type of the values
384 \param x Value x
385 \param y Value y
386 TODO: adl1995 - Merge this function with
387 formulas/vertex_longitude.hpp
388 */
389 template<typename ValueType>
normalize_unit_vector(ValueType & x,ValueType & y)390 inline void normalize_unit_vector(ValueType& x, ValueType& y)
391 {
392     ValueType h = boost::math::hypot(x, y);
393 
394     BOOST_GEOMETRY_ASSERT(h > 0);
395 
396     x /= h; y /= h;
397 }
398 
399 /*!
400 \brief Short utility to calculate difference between two longitudes
401        normalized in range (-180, 180].
402 \tparam Units The units of the coordindate system in the spheroid
403 \tparam CoordinateType The type of the coordinates
404 \param longitude1 Longitude 1
405 \param longitude2 Longitude 2
406 \ingroup utility
407 */
408 template <typename Units, typename CoordinateType>
longitude_distance_signed(CoordinateType const & longitude1,CoordinateType const & longitude2)409 inline CoordinateType longitude_distance_signed(CoordinateType const& longitude1,
410                                                 CoordinateType const& longitude2)
411 {
412     CoordinateType diff = longitude2 - longitude1;
413     math::normalize_longitude<Units, CoordinateType>(diff);
414     return diff;
415 }
416 
417 
418 /*!
419 \brief Short utility to calculate difference between two longitudes
420        normalized in range [0, 360).
421 \tparam Units The units of the coordindate system in the spheroid
422 \tparam CoordinateType The type of the coordinates
423 \param longitude1 Longitude 1
424 \param longitude2 Longitude 2
425 \ingroup utility
426 */
427 template <typename Units, typename CoordinateType>
longitude_distance_unsigned(CoordinateType const & longitude1,CoordinateType const & longitude2)428 inline CoordinateType longitude_distance_unsigned(CoordinateType const& longitude1,
429                                                   CoordinateType const& longitude2)
430 {
431     typedef math::detail::constants_on_spheroid
432         <
433             CoordinateType, Units
434         > constants;
435 
436     CoordinateType const c0 = 0;
437     CoordinateType diff = longitude_distance_signed<Units>(longitude1, longitude2);
438     if (diff < c0) // (-180, 180] -> [0, 360)
439     {
440         diff += constants::period();
441     }
442     return diff;
443 }
444 
445 /*!
446 \brief The abs difference between longitudes in range [0, 180].
447 \tparam Units The units of the coordindate system in the spheroid
448 \tparam CoordinateType The type of the coordinates
449 \param longitude1 Longitude 1
450 \param longitude2 Longitude 2
451 \ingroup utility
452 */
453 template <typename Units, typename CoordinateType>
longitude_difference(CoordinateType const & longitude1,CoordinateType const & longitude2)454 inline CoordinateType longitude_difference(CoordinateType const& longitude1,
455                                            CoordinateType const& longitude2)
456 {
457     return math::abs(math::longitude_distance_signed<Units>(longitude1, longitude2));
458 }
459 
460 template <typename Units, typename CoordinateType>
longitude_interval_distance_signed(CoordinateType const & longitude_a1,CoordinateType const & longitude_a2,CoordinateType const & longitude_b)461 inline CoordinateType longitude_interval_distance_signed(CoordinateType const& longitude_a1,
462                                                          CoordinateType const& longitude_a2,
463                                                          CoordinateType const& longitude_b)
464 {
465     CoordinateType const c0 = 0;
466     CoordinateType dist_a12 = longitude_distance_signed<Units>(longitude_a1, longitude_a2);
467     CoordinateType dist_a1b = longitude_distance_signed<Units>(longitude_a1, longitude_b);
468     if (dist_a12 < c0)
469     {
470         dist_a12 = -dist_a12;
471         dist_a1b = -dist_a1b;
472     }
473 
474     return dist_a1b < c0 ? dist_a1b
475          : dist_a1b > dist_a12 ? dist_a1b - dist_a12
476          : c0;
477 }
478 
479 
480 } // namespace math
481 
482 
483 }} // namespace boost::geometry
484 
485 #endif // BOOST_GEOMETRY_UTIL_NORMALIZE_SPHEROIDAL_COORDINATES_HPP
486