centroid_bashein_detmer.hpp 8.3 KB

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