centroid_bashein_detmer.hpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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 Point point type of centroid to calculate
  36. \tparam PointOfSegment point type of segments, defaults to Point
  37. \tparam CalculationType \tparam_calculation
  38. \author Adapted from "Centroid of a Polygon" by
  39. Gerard Bashein and Paul R. Detmer<em>,
  40. in "Graphics Gems IV", Academic Press, 1994</em>
  41. \qbk{
  42. [heading See also]
  43. [link geometry.reference.algorithms.centroid.centroid_3_with_strategy centroid (with strategy)]
  44. }
  45. */
  46. /*
  47. \par Research notes
  48. The algorithm gives the same results as Oracle and PostGIS but
  49. differs from MySQL
  50. (tried 5.0.21 / 5.0.45 / 5.0.51a / 5.1.23).
  51. Without holes:
  52. - this: POINT(4.06923363095238 1.65055803571429)
  53. - geolib: POINT(4.07254 1.66819)
  54. - MySQL: POINT(3.6636363636364 1.6272727272727)'
  55. - PostGIS: POINT(4.06923363095238 1.65055803571429)
  56. - Oracle: 4.06923363095238 1.65055803571429
  57. - SQL Server: POINT(4.06923362245959 1.65055804168294)
  58. Statements:
  59. - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText(
  60. '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
  61. ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))')))
  62. - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null,
  63. sdo_elem_info_array(1, 1003, 1), sdo_ordinate_array(
  64. 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
  65. ,5.4,1.2,4.9,0.8,2.9,0.7,2,1.3))
  66. , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005)
  67. ,mdsys.sdo_dim_element('y',0,10,.00000005)))
  68. from dual
  69. - \b SQL Server 2008: select geometry::STGeomFromText(
  70. '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
  71. ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))',0)
  72. .STCentroid()
  73. .STAsText()
  74. With holes:
  75. - this: POINT(4.04663 1.6349)
  76. - geolib: POINT(4.04675 1.65735)
  77. - MySQL: POINT(3.6090580503834 1.607573932092)
  78. - PostGIS: POINT(4.0466265060241 1.63489959839357)
  79. - Oracle: 4.0466265060241 1.63489959839357
  80. - SQL Server: POINT(4.0466264962959677 1.6348996057331333)
  81. Statements:
  82. - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText(
  83. 'POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2
  84. ,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)
  85. ,(4 2,4.2 1.4,4.8 1.9,4.4 2.2,4 2))')));
  86. - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null
  87. , sdo_elem_info_array(1, 1003, 1, 25, 2003, 1)
  88. , sdo_ordinate_array(2,1.3,2.4,1.7,2.8,1.8,3.4,1.2,3.7,1.6,3.4,
  89. 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,
  90. 4.8,1.9, 4.4,2.2, 4,2))
  91. , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005)
  92. ,mdsys.sdo_dim_element('y',0,10,.00000005)))
  93. from dual
  94. */
  95. template
  96. <
  97. typename Ignored1 = void,
  98. typename Ignored2 = void,
  99. typename CalculationType = void
  100. >
  101. class bashein_detmer
  102. {
  103. private :
  104. // If user specified a calculation type, use that type,
  105. // whatever it is and whatever the point-type(s) are.
  106. // Else, use the most appropriate coordinate type
  107. // of the two points, but at least double
  108. template <typename GeometryPoint, typename ResultPoint>
  109. struct calculation_type
  110. : std::conditional
  111. <
  112. std::is_void<CalculationType>::value,
  113. typename select_most_precise
  114. <
  115. typename coordinate_type<GeometryPoint>::type,
  116. typename coordinate_type<ResultPoint>::type,
  117. double
  118. >::type,
  119. CalculationType
  120. >
  121. {};
  122. /*! subclass to keep state */
  123. template <typename GeometryPoint, typename ResultPoint>
  124. class sums
  125. {
  126. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  127. friend class bashein_detmer;
  128. std::size_t count;
  129. calc_type sum_a2;
  130. calc_type sum_x;
  131. calc_type sum_y;
  132. public :
  133. inline sums()
  134. : count(0)
  135. , sum_a2(calc_type())
  136. , sum_x(calc_type())
  137. , sum_y(calc_type())
  138. {}
  139. };
  140. public :
  141. template <typename GeometryPoint, typename ResultPoint>
  142. struct state_type
  143. {
  144. typedef sums<GeometryPoint, ResultPoint> type;
  145. };
  146. template <typename GeometryPoint, typename ResultPoint>
  147. static inline void apply(GeometryPoint const& p1, GeometryPoint const& p2,
  148. sums<GeometryPoint, ResultPoint>& state)
  149. {
  150. /* Algorithm:
  151. For each segment:
  152. begin
  153. ai = x1 * y2 - x2 * y1;
  154. sum_a2 += ai;
  155. sum_x += ai * (x1 + x2);
  156. sum_y += ai * (y1 + y2);
  157. end
  158. return POINT(sum_x / (3 * sum_a2), sum_y / (3 * sum_a2) )
  159. */
  160. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  161. // Get coordinates and promote them to calculation_type
  162. calc_type const x1 = util::numeric_cast<calc_type>(get<0>(p1));
  163. calc_type const y1 = util::numeric_cast<calc_type>(get<1>(p1));
  164. calc_type const x2 = util::numeric_cast<calc_type>(get<0>(p2));
  165. calc_type const y2 = util::numeric_cast<calc_type>(get<1>(p2));
  166. calc_type const ai = geometry::detail::determinant<calc_type>(p1, p2);
  167. state.count++;
  168. state.sum_a2 += ai;
  169. state.sum_x += ai * (x1 + x2);
  170. state.sum_y += ai * (y1 + y2);
  171. }
  172. template <typename GeometryPoint, typename ResultPoint>
  173. static inline bool result(sums<GeometryPoint, ResultPoint> const& state,
  174. ResultPoint& centroid)
  175. {
  176. typedef typename calculation_type<GeometryPoint, ResultPoint>::type calc_type;
  177. calc_type const zero = calc_type();
  178. if (state.count > 0 && ! math::equals(state.sum_a2, zero))
  179. {
  180. calc_type const v3 = 3;
  181. calc_type const a3 = v3 * state.sum_a2;
  182. typedef typename geometry::coordinate_type
  183. <
  184. ResultPoint
  185. >::type coordinate_type;
  186. // Prevent NaN centroid coordinates
  187. if (boost::math::isfinite(a3))
  188. {
  189. // NOTE: above calculation_type is checked, not the centroid coordinate_type
  190. // which means that the centroid can still be filled with INF
  191. // if e.g. calculation_type is double and centroid contains floats
  192. set<0>(centroid,
  193. util::numeric_cast<coordinate_type>(state.sum_x / a3));
  194. set<1>(centroid,
  195. util::numeric_cast<coordinate_type>(state.sum_y / a3));
  196. return true;
  197. }
  198. }
  199. return false;
  200. }
  201. };
  202. #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  203. namespace services
  204. {
  205. // Register this strategy for rings and (multi)polygons, in two dimensions
  206. template <typename Point, typename Geometry>
  207. struct default_strategy<cartesian_tag, areal_tag, 2, Point, Geometry>
  208. {
  209. typedef bashein_detmer
  210. <
  211. Point,
  212. typename point_type<Geometry>::type
  213. > type;
  214. };
  215. } // namespace services
  216. #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  217. }} // namespace strategy::centroid
  218. }} // namespace boost::geometry
  219. #endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP