centroid_bashein_detmer.hpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. // Boost.Geometry (aka GGL, Generic Geometry Library)
  2. // Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
  3. // Copyright (c) 2008-2015 Bruno Lalande, Paris, France.
  4. // Copyright (c) 2009-2015 Mateusz Loskot, London, UK.
  5. // This file was modified by Oracle on 2015-2021.
  6. // Modifications copyright (c) 2015-2021, Oracle and/or its affiliates.
  7. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
  8. // Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
  9. // (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.
  10. // Use, modification and distribution is subject to the Boost Software License,
  11. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  12. // http://www.boost.org/LICENSE_1_0.txt)
  13. #ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP
  14. #define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP
  15. #include <cstddef>
  16. #include <boost/math/special_functions/fpclassify.hpp>
  17. #include <boost/geometry/arithmetic/determinant.hpp>
  18. #include <boost/geometry/core/coordinate_type.hpp>
  19. #include <boost/geometry/core/point_type.hpp>
  20. #include <boost/geometry/strategies/centroid.hpp>
  21. #include <boost/geometry/util/math.hpp>
  22. #include <boost/geometry/util/numeric_cast.hpp>
  23. #include <boost/geometry/util/select_most_precise.hpp>
  24. namespace boost { namespace geometry
  25. {
  26. // Note: when calling the namespace "centroid", it sometimes,
  27. // somehow, in gcc, gives compilation problems (confusion with function centroid).
  28. namespace strategy { namespace centroid
  29. {
  30. /*!
  31. \brief Centroid calculation using algorithm Bashein / Detmer
  32. \ingroup strategies
  33. \details Calculates centroid using triangulation method published by
  34. Bashein / Detmer
  35. \tparam CalculationType \tparam_calculation
  36. \author Adapted from "Centroid of a Polygon" by
  37. Gerard Bashein and Paul R. Detmer<em>,
  38. in "Graphics Gems IV", Academic Press, 1994</em>
  39. \qbk{
  40. [heading See also]
  41. [link geometry.reference.algorithms.centroid.centroid_3_with_strategy centroid (with strategy)]
  42. }
  43. */
  44. /*
  45. \par Research notes
  46. The algorithm gives the same results as Oracle and PostGIS but
  47. differs from MySQL
  48. (tried 5.0.21 / 5.0.45 / 5.0.51a / 5.1.23).
  49. Without holes:
  50. - this: POINT(4.06923363095238 1.65055803571429)
  51. - geolib: POINT(4.07254 1.66819)
  52. - MySQL: POINT(3.6636363636364 1.6272727272727)'
  53. - PostGIS: POINT(4.06923363095238 1.65055803571429)
  54. - Oracle: 4.06923363095238 1.65055803571429
  55. - SQL Server: POINT(4.06923362245959 1.65055804168294)
  56. Statements:
  57. - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText(
  58. 'POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6
  59. ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))')))
  60. - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null,
  61. sdo_elem_info_array(1, 1003, 1), sdo_ordinate_array(
  62. 2,1.3,2.4,1.7,2.8,1.8,3.4,1.2,3.7,1.6,3.4,2,4.1,3,5.3,2.6
  63. ,5.4,1.2,4.9,0.8,2.9,0.7,2,1.3))
  64. , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005)
  65. ,mdsys.sdo_dim_element('y',0,10,.00000005)))
  66. from dual
  67. - \b SQL Server 2008: select geometry::STGeomFromText(
  68. 'POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6
  69. ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))',0)
  70. .STCentroid()
  71. .STAsText()
  72. With holes:
  73. - this: POINT(4.04663 1.6349)
  74. - geolib: POINT(4.04675 1.65735)
  75. - MySQL: POINT(3.6090580503834 1.607573932092)
  76. - PostGIS: POINT(4.0466265060241 1.63489959839357)
  77. - Oracle: 4.0466265060241 1.63489959839357
  78. - SQL Server: POINT(4.0466264962959677 1.6348996057331333)
  79. Statements:
  80. - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText(
  81. 'POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2
  82. ,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3)
  83. ,(4 2,4.2 1.4,4.8 1.9,4.4 2.2,4 2))')));
  84. - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null
  85. , sdo_elem_info_array(1, 1003, 1, 25, 2003, 1)
  86. , sdo_ordinate_array(2,1.3,2.4,1.7,2.8,1.8,3.4,1.2,3.7,1.6,3.4,
  87. 2,4.1,3,5.3,2.6,5.4,1.2,4.9,0.8,2.9,0.7,2,1.3,4,2, 4.2,1.4,
  88. 4.8,1.9, 4.4,2.2, 4,2))
  89. , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005)
  90. ,mdsys.sdo_dim_element('y',0,10,.00000005)))
  91. from dual
  92. */
  93. template
  94. <
  95. typename Ignored1 = void,
  96. typename Ignored2 = void,
  97. typename CalculationType = void
  98. >
  99. class bashein_detmer
  100. {
  101. private :
  102. // If user specified a calculation type, use that type,
  103. // whatever it is and whatever the point-type(s) are.
  104. // Else, use the most appropriate coordinate type
  105. // of the two points, but at least double
  106. template <typename GeometryPoint, typename ResultPoint>
  107. struct calculation_type
  108. : std::conditional
  109. <
  110. std::is_void<CalculationType>::value,
  111. typename select_most_precise
  112. <
  113. coordinate_type_t<GeometryPoint>,
  114. coordinate_type_t<ResultPoint>,
  115. double
  116. >::type,
  117. CalculationType
  118. >
  119. {};
  120. /*! subclass to keep state */
  121. template <typename GeometryPoint, typename ResultPoint>
  122. class sums
  123. {
  124. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  125. friend class bashein_detmer;
  126. std::size_t count;
  127. calc_type sum_a2;
  128. calc_type sum_x;
  129. calc_type sum_y;
  130. public :
  131. inline sums()
  132. : count(0)
  133. , sum_a2(calc_type())
  134. , sum_x(calc_type())
  135. , sum_y(calc_type())
  136. {}
  137. };
  138. public :
  139. template <typename GeometryPoint, typename ResultPoint>
  140. struct state_type
  141. {
  142. typedef sums<GeometryPoint, ResultPoint> type;
  143. };
  144. template <typename GeometryPoint, typename ResultPoint>
  145. static inline void apply(GeometryPoint const& p1, GeometryPoint const& p2,
  146. sums<GeometryPoint, ResultPoint>& state)
  147. {
  148. /* Algorithm:
  149. For each segment:
  150. begin
  151. ai = x1 * y2 - x2 * y1;
  152. sum_a2 += ai;
  153. sum_x += ai * (x1 + x2);
  154. sum_y += ai * (y1 + y2);
  155. end
  156. return POINT(sum_x / (3 * sum_a2), sum_y / (3 * sum_a2) )
  157. */
  158. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  159. // Get coordinates and promote them to calculation_type
  160. calc_type const x1 = util::numeric_cast<calc_type>(get<0>(p1));
  161. calc_type const y1 = util::numeric_cast<calc_type>(get<1>(p1));
  162. calc_type const x2 = util::numeric_cast<calc_type>(get<0>(p2));
  163. calc_type const y2 = util::numeric_cast<calc_type>(get<1>(p2));
  164. calc_type const ai = geometry::detail::determinant<calc_type>(p1, p2);
  165. state.count++;
  166. state.sum_a2 += ai;
  167. state.sum_x += ai * (x1 + x2);
  168. state.sum_y += ai * (y1 + y2);
  169. }
  170. template <typename GeometryPoint, typename ResultPoint>
  171. static inline bool result(sums<GeometryPoint, ResultPoint> const& state,
  172. ResultPoint& centroid)
  173. {
  174. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  175. calc_type const zero = calc_type();
  176. if (state.count > 0 && ! math::equals(state.sum_a2, zero))
  177. {
  178. calc_type const v3 = 3;
  179. calc_type const a3 = v3 * state.sum_a2;
  180. using coordinate_type = geometry::coordinate_type_t<ResultPoint>;
  181. // Prevent NaN centroid coordinates
  182. if (boost::math::isfinite(a3))
  183. {
  184. // NOTE: above calculation_type is checked, not the centroid coordinate_type
  185. // which means that the centroid can still be filled with INF
  186. // if e.g. calculation_type is double and centroid contains floats
  187. set<0>(centroid,
  188. util::numeric_cast<coordinate_type>(state.sum_x / a3));
  189. set<1>(centroid,
  190. util::numeric_cast<coordinate_type>(state.sum_y / a3));
  191. return true;
  192. }
  193. }
  194. return false;
  195. }
  196. };
  197. #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  198. namespace services
  199. {
  200. // Register this strategy for rings and (multi)polygons, in two dimensions
  201. template <typename Point, typename Geometry>
  202. struct default_strategy<cartesian_tag, areal_tag, 2, Point, Geometry>
  203. {
  204. typedef bashein_detmer
  205. <
  206. Point,
  207. point_type_t<Geometry>
  208. > type;
  209. };
  210. } // namespace services
  211. #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  212. }} // namespace strategy::centroid
  213. }} // namespace boost::geometry
  214. #endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP