// Boost.Geometry (aka GGL, Generic Geometry Library) // Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands. // Copyright (c) 2008-2012 Bruno Lalande, Paris, France. // Copyright (c) 2009-2012 Mateusz Loskot, London, UK. // Parts of Boost.Geometry are redesigned from Geodan's Geographic Library // (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands. // Use, modification and distribution is subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP #define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP #include #include #include #include #include #include #include #include namespace boost { namespace geometry { // Note: when calling the namespace "centroid", it sometimes, // somehow, in gcc, gives compilation problems (confusion with function centroid). namespace strategy { namespace centroid { /*! \brief Centroid calculation using algorithm Bashein / Detmer \ingroup strategies \details Calculates centroid using triangulation method published by Bashein / Detmer \tparam Point point type of centroid to calculate \tparam PointOfSegment point type of segments, defaults to Point \tparam CalculationType \tparam_calculation \author Adapted from "Centroid of a Polygon" by Gerard Bashein and Paul R. Detmer, in "Graphics Gems IV", Academic Press, 1994 \qbk{ [heading See also] [link geometry.reference.algorithms.centroid.centroid_3_with_strategy centroid (with strategy)] } */ /* \par Research notes The algorithm gives the same results as Oracle and PostGIS but differs from MySQL (tried 5.0.21 / 5.0.45 / 5.0.51a / 5.1.23). Without holes: - this: POINT(4.06923363095238 1.65055803571429) - geolib: POINT(4.07254 1.66819) - MySQL: POINT(3.6636363636364 1.6272727272727)' - PostGIS: POINT(4.06923363095238 1.65055803571429) - Oracle: 4.06923363095238 1.65055803571429 - SQL Server: POINT(4.06923362245959 1.65055804168294) Statements: - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText( '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 ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))'))) - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null, sdo_elem_info_array(1, 1003, 1), sdo_ordinate_array( 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 ,5.4,1.2,4.9,0.8,2.9,0.7,2,1.3)) , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005) ,mdsys.sdo_dim_element('y',0,10,.00000005))) from dual - \b SQL Server 2008: select geometry::STGeomFromText( '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 ,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))',0) .STCentroid() .STAsText() With holes: - this: POINT(4.04663 1.6349) - geolib: POINT(4.04675 1.65735) - MySQL: POINT(3.6090580503834 1.607573932092) - PostGIS: POINT(4.0466265060241 1.63489959839357) - Oracle: 4.0466265060241 1.63489959839357 - SQL Server: POINT(4.0466264962959677 1.6348996057331333) Statements: - \b MySQL/PostGIS: select AsText(Centroid(GeomFromText( '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,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3) ,(4 2,4.2 1.4,4.8 1.9,4.4 2.2,4 2))'))); - \b Oracle: select sdo_geom.sdo_centroid(sdo_geometry(2003, null, null , sdo_elem_info_array(1, 1003, 1, 25, 2003, 1) , sdo_ordinate_array(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,5.4,1.2,4.9,0.8,2.9,0.7,2,1.3,4,2, 4.2,1.4, 4.8,1.9, 4.4,2.2, 4,2)) , mdsys.sdo_dim_array(mdsys.sdo_dim_element('x',0,10,.00000005) ,mdsys.sdo_dim_element('y',0,10,.00000005))) from dual */ template < typename Point, typename PointOfSegment = Point, typename CalculationType = void > class bashein_detmer { private : // If user specified a calculation type, use that type, // whatever it is and whatever the point-type(s) are. // Else, use the most appropriate coordinate type // of the two points, but at least double typedef typename boost::mpl::if_c < boost::is_void::type::value, typename select_most_precise < typename select_coordinate_type < Point, PointOfSegment >::type, double >::type, CalculationType >::type calculation_type; /*! subclass to keep state */ class sums { friend class bashein_detmer; int count; calculation_type sum_a2; calculation_type sum_x; calculation_type sum_y; public : inline sums() : count(0) , sum_a2(calculation_type()) , sum_x(calculation_type()) , sum_y(calculation_type()) {} }; public : typedef sums state_type; static inline void apply(PointOfSegment const& p1, PointOfSegment const& p2, sums& state) { /* Algorithm: For each segment: begin ai = x1 * y2 - x2 * y1; sum_a2 += ai; sum_x += ai * (x1 + x2); sum_y += ai * (y1 + y2); end return POINT(sum_x / (3 * sum_a2), sum_y / (3 * sum_a2) ) */ // Get coordinates and promote them to calculation_type calculation_type const x1 = boost::numeric_cast(get<0>(p1)); calculation_type const y1 = boost::numeric_cast(get<1>(p1)); calculation_type const x2 = boost::numeric_cast(get<0>(p2)); calculation_type const y2 = boost::numeric_cast(get<1>(p2)); calculation_type const ai = geometry::detail::determinant(p1, p2); state.count++; state.sum_a2 += ai; state.sum_x += ai * (x1 + x2); state.sum_y += ai * (y1 + y2); } static inline bool result(sums const& state, Point& centroid) { calculation_type const zero = calculation_type(); if (state.count > 0 && ! math::equals(state.sum_a2, zero)) { calculation_type const v3 = 3; calculation_type const a3 = v3 * state.sum_a2; typedef typename geometry::coordinate_type < Point >::type coordinate_type; set<0>(centroid, boost::numeric_cast(state.sum_x / a3)); set<1>(centroid, boost::numeric_cast(state.sum_y / a3)); return true; } return false; } }; #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS namespace services { // Register this strategy for rings and (multi)polygons, in two dimensions template struct default_strategy { typedef bashein_detmer < Point, typename point_type::type > type; }; } // namespace services #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS }} // namespace strategy::centroid }} // namespace boost::geometry #endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_CENTROID_BASHEIN_DETMER_HPP