Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-21 08:45:50

0001  // Boost.Geometry
0002 
0003 // Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands.
0004 
0005 // Use, modification and distribution is subject to the Boost Software License,
0006 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
0007 // http://www.boost.org/LICENSE_1_0.txt)
0008 
0009 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_ASSIGN_CLUSTERED_COUNTS_HPP
0010 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_ASSIGN_CLUSTERED_COUNTS_HPP
0011 
0012 #include <boost/geometry/algorithms/detail/position_code.hpp>
0013 #include <boost/geometry/algorithms/detail/overlay/approximately_equals.hpp>
0014 #include <boost/geometry/algorithms/detail/signed_size_type.hpp>
0015 #include <boost/geometry/algorithms/detail/overlay/cluster_info.hpp>
0016 #include <boost/geometry/algorithms/detail/overlay/copy_segment_point.hpp>
0017 #include <boost/geometry/algorithms/detail/overlay/get_distance_measure.hpp>
0018 #include <boost/geometry/algorithms/detail/overlay/overlay_type.hpp>
0019 #include <boost/geometry/algorithms/detail/overlay/segment_identifier.hpp>
0020 
0021 #include <set>
0022 #include <map>
0023 
0024 namespace boost { namespace geometry
0025 {
0026 
0027 #ifndef DOXYGEN_NO_DETAIL
0028 namespace detail { namespace overlay
0029 {
0030 
0031 // Indicating if the segment is incoming (to cluster) or outgoing (from cluster)
0032 enum class connection_type { unknown = -1, incoming = 0, outgoing = 1 };
0033 
0034 // A turn contains four connections to a cluster:
0035 // For both operations one incoming and one outgoing connection.
0036 // They are stored in a map, with the (segment id, connection type) as key.
0037 struct connection_key
0038 {
0039     segment_identifier seg_id;
0040     connection_type connection{connection_type::unknown};
0041 
0042     bool operator<(connection_key const& rhs) const
0043     {
0044         return std::tie(seg_id, connection) < std::tie(rhs.seg_id, rhs.connection);
0045     }
0046 };
0047 
0048 // Properties of a connection in a property map
0049 template <typename Point>
0050 struct connection_properties
0051 {
0052     // Assigned at construction time
0053     int position_code{0};
0054     Point point{};
0055     Point opposite_point{};
0056     bool is_shifted{false};
0057 
0058     // Assigned later
0059     std::size_t zone_count_left{0};
0060     std::size_t zone_count_right{0};
0061     std::size_t rank{0};
0062 };
0063 
0064 // Convenience structure to store connections in a vector
0065 template <typename Point>
0066 struct connection_item
0067 {
0068     connection_key key{};
0069     connection_properties<Point> properties{};
0070 };
0071 
0072 template <overlay_type OverlayType>
0073 struct is_corresponding_connection
0074 {
0075     static inline bool apply(connection_key const& left, connection_key const& right)
0076     {
0077         return left.seg_id.source_index == right.seg_id.source_index;
0078     }
0079 };
0080 
0081 template <>
0082 struct is_corresponding_connection<overlay_buffer>
0083 {
0084     static inline bool apply(connection_key const& left, connection_key const& right)
0085     {
0086         // For buffer, the source_index is always the same.
0087         // It needs to check where the incoming seg_id is outgoing.
0088         return left.seg_id == right.seg_id;
0089     }
0090 };
0091 
0092 template
0093 <
0094     bool Reverse1,
0095     bool Reverse2,
0096     overlay_type OverlayType,
0097     typename Geometry1,
0098     typename Geometry2,
0099     typename Turns,
0100     typename Clusters,
0101     typename Strategy
0102 >
0103 struct clustered_count_handler
0104 {
0105     using point_type = typename Turns::value_type::point_type;
0106     using connection_map_type = std::map<connection_key, connection_properties<point_type>>;
0107     using ct_type = typename geometry::select_most_precise
0108         <
0109             geometry::coordinate_type_t<point_type>,
0110             double
0111         >::type;
0112 
0113     clustered_count_handler(Geometry1 const& m_geometry1, Geometry2 const& m_geometry2,
0114         Turns& m_turns, Clusters& clusters,
0115         Strategy const& strategy)
0116         : m_geometry1(m_geometry1)
0117         , m_geometry2(m_geometry2)
0118         , m_turns(m_turns)
0119         , m_clusters(clusters)
0120         , m_intersection_strategy(strategy)
0121         , m_side_strategy(m_intersection_strategy.side())
0122     {}
0123 
0124     // Walks over a ring to get the point after the turn.
0125     // The turn can be located at the very end of a segment.
0126     // Therefore it can be the first point on the next segment.
0127     template <typename Operation>
0128     bool get_segment_points(Operation const& op, point_type const& point_turn,
0129         point_type& point_from, point_type& point_to)
0130     {
0131         // Use the coordinate type, but if it is too small (e.g. std::int16), use a double
0132         static const ct_type tolerance
0133             = common_approximately_equals_epsilon_multiplier<ct_type>::value();
0134 
0135         // For a defensive check.
0136         constexpr int max_iterations = 10;
0137 
0138         int from_offset = 0;
0139         do
0140         {
0141             geometry::copy_segment_point<Reverse1, Reverse2>(m_geometry1, m_geometry2,
0142                 op.seg_id, from_offset--, point_from);
0143         } while (approximately_equals(point_from, point_turn, tolerance) && from_offset > -max_iterations);
0144 
0145         int to_offset = 1;
0146         do
0147         {
0148             geometry::copy_segment_point<Reverse1, Reverse2>(m_geometry1, m_geometry2,
0149                 op.seg_id, to_offset++, point_to);
0150         } while (approximately_equals(point_to, point_turn, tolerance) && to_offset < max_iterations);
0151 
0152         return from_offset < -1 || to_offset > 2;
0153     }
0154 
0155     void get_connection_map(cluster_info const& cluster, point_type const& point_turn,
0156         connection_map_type& connection_map, point_type& point_origin)
0157     {
0158         auto const get_position_code = [&](point_type const& point)
0159         {
0160             return detail::get_position_code(point_origin, point_turn, point, m_side_strategy);
0161         };
0162 
0163         auto insert = [&connection_map](auto const& op,  connection_type conn,
0164                     auto const& point, int position_code, auto const& opposite_point, bool is_shifted)
0165         {
0166             connection_key const key{op.seg_id, conn};
0167             connection_properties<point_type> properties{position_code, point, opposite_point, is_shifted};
0168             connection_map.insert({key, properties});
0169         };
0170 
0171         // Add them to the set, which keeps them unique on (seg_id,from/to)
0172         bool first = true;
0173         for (std::size_t index : cluster.turn_indices)
0174         {
0175             auto const& turn = m_turns[index];
0176             for (auto const& op : turn.operations)
0177             {
0178                 point_type point_from, point_to;
0179                 bool const is_shifted = get_segment_points(op, point_turn, point_from, point_to);
0180 
0181                 if (first)
0182                 {
0183                     // One of the incoming points is the origin. For the algorithm,
0184                     // it does not matter which one.
0185                     first = false;
0186                     point_origin = point_from;
0187                 }
0188 
0189                 // Insert the four connections. Insert all operations (even if they are blocked).
0190                 insert(op, connection_type::incoming, point_from, get_position_code(point_from), point_to, is_shifted);
0191                 insert(op, connection_type::outgoing, point_to, get_position_code(point_to), point_from, is_shifted);
0192             }
0193         }
0194     }
0195 
0196     void sort(point_type const& point_turn, std::vector<connection_item<point_type>>& item_vector)
0197     {
0198         auto compare_by_connection = [](auto const& left, auto const& right)
0199         {
0200             // Reversing it gives only one failure in ticket_9942 (difference)...
0201             return left.key.connection > right.key.connection;
0202         };
0203 
0204         // Compare by side, then by connection.
0205         // Left-side (1) goes before right-side (-1).
0206         // Outgoing (1) goes before incoming (0).
0207         auto compare_by_side = [&](auto const& left, auto const& right)
0208         {
0209             int const side_left = m_side_strategy.apply(point_turn, right.properties.point, left.properties.point);
0210             int const side_right = m_side_strategy.apply(point_turn, left.properties.point, right.properties.point);
0211 
0212             if (side_right == side_left)
0213             {
0214                 return compare_by_connection(left, right);
0215             }
0216             return side_left < side_right;
0217         };
0218 
0219         std::sort(item_vector.begin(), item_vector.end(),
0220             [&](auto const& left, auto const& right)
0221             {
0222                 if (left.properties.position_code == right.properties.position_code)
0223                 {
0224                     if (left.properties.position_code == 1 || left.properties.position_code == 3)
0225                     {
0226                         // For collinear cases, side is be the same.
0227                         return compare_by_connection(left, right);
0228                     }
0229                     return compare_by_side(left, right);
0230                 }
0231                 return left.properties.position_code < right.properties.position_code;
0232             });
0233     }
0234 
0235     // Assign ranks, counter clockwise from the first incoming segment.
0236     void assign_ranks(point_type const& point_turn,
0237         std::vector<connection_item<point_type>>& item_vector)
0238     {
0239         std::size_t rank = 0;
0240         item_vector.front().properties.rank = 0;
0241         for (std::size_t i = 0; i + 1 < item_vector.size(); i++)
0242         {
0243             auto const& previous = item_vector[i];
0244             auto& item = item_vector[i + 1];
0245             if (item.properties.position_code != previous.properties.position_code)
0246             {
0247                 item.properties.rank = ++rank;
0248                 continue;
0249             }
0250 
0251             if (item.properties.position_code == 1 || item.properties.position_code == 3)
0252             {
0253                 // Collinear cases always get the same rank.
0254                 item.properties.rank = rank;
0255                 continue;
0256             }
0257 
0258             // If it is collinear, it gets the same rank.
0259             // In other cases the side should be 1 (left) because the connections
0260             // are sorted counter clockwise.
0261             int const side = m_side_strategy.apply(point_turn, previous.properties.point,
0262                 item.properties.point);
0263             item.properties.rank = side == 0 ? rank : ++rank;
0264         }
0265     }
0266 
0267     auto get_zone_counts(std::vector<connection_item<point_type>> const& item_vector,
0268         std::size_t rank_size)
0269     {
0270         std::size_t const vector_size = item_vector.size();
0271         auto get_next_item = [&vector_size](std::size_t counter)
0272         {
0273             return (counter + 1) % vector_size;
0274         };
0275 
0276         auto get_next_zone = [&rank_size](std::size_t counter)
0277         {
0278             return (counter + 1) % rank_size;
0279         };
0280 
0281         // Each segment occurs twice, once as from, once as to.
0282         // As soon as it comes in, increase the zone count, until it goes out.
0283         std::vector<std::size_t> zone_counts(rank_size, 0);
0284         for (std::size_t i = 0; i < item_vector.size(); i++)
0285         {
0286             auto const& item = item_vector[i];
0287             if (item.key.connection != connection_type::incoming)
0288             {
0289                 continue;
0290             }
0291 
0292             // Walk ahead, cyclic, to find the next item with the same seg_id.
0293             // The iteration is a defensive check.
0294             std::size_t end_rank = item.properties.rank;
0295             for (std::size_t j = get_next_item(i), iteration = 0; ; j = get_next_item(j), iteration++)
0296             {
0297                 if (iteration > vector_size)
0298                 {
0299 #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH)
0300                     std::cerr << " *** ERROR: infinite loop in cluster" << std::endl;
0301 #endif                    
0302                     return zone_counts;
0303                 }
0304                 auto const& next = item_vector[j];
0305                 end_rank = next.properties.rank;
0306 
0307                 if (next.key.connection == connection_type::outgoing
0308                     && is_corresponding_connection<OverlayType>::apply(item.key, next.key))
0309                 {
0310                     // Found the corresponding outgoing segment for this incoming segment.
0311                     break;
0312                 }
0313             }
0314 
0315             // Assign the ring count to the zone_counts in the rank range.
0316             for (std::size_t r = item.properties.rank; r != end_rank; r = get_next_zone(r))
0317             {
0318                 zone_counts[r]++;
0319             }
0320         }
0321 
0322         return zone_counts;
0323     }
0324 
0325     void assign_zone_counts(std::vector<connection_item<point_type>>& item_vector,
0326         std::vector<std::size_t> const& zone_counts, std::size_t rank_size)
0327     {
0328         // The main goal is to get the number of polygons in the zone_counts.
0329         // The zone_counts on the right side of the seg_ids.
0330         for (auto& item : item_vector)
0331         {
0332             std::size_t const zone_right =
0333                 item.key.connection == connection_type::incoming
0334                 ? item.properties.rank
0335                 : (item.properties.rank + rank_size - 1) % rank_size;
0336 
0337             std::size_t const zone_left =
0338                 item.key.connection == connection_type::incoming
0339                 ? (item.properties.rank + rank_size - 1) % rank_size
0340                 : item.properties.rank;
0341 
0342             item.properties.zone_count_left = zone_counts[zone_left];
0343             item.properties.zone_count_right = zone_counts[zone_right];
0344         }
0345     }
0346 
0347     std::size_t get_open_count(std::vector<std::size_t> const& zone_counts, std::size_t rank_size)
0348     {
0349         std::size_t result = 0;
0350         for (std::size_t i = 0; i < rank_size; i++)
0351         {
0352             if (zone_counts[i] == 0)
0353             {
0354                 result++;
0355             }
0356         }
0357         return result;
0358     }
0359 
0360     // Get the number of spikes in a cluster, and mark them as spikes.
0361     void handle_spikes(cluster_info& cluster, std::vector<connection_item<point_type>>& item_vector)
0362     {
0363         for (std::size_t i = 0; i < item_vector.size(); i++)
0364         {
0365             auto const next_i = (i + 1) % item_vector.size();
0366             if (item_vector[i].key.connection == item_vector[next_i].key.connection)
0367             {
0368                 // The connection should be different
0369                 continue;
0370             }
0371             auto& current = item_vector[i].properties;
0372             auto& next = item_vector[next_i].properties;
0373             if (current.rank != next.rank
0374                 || current.zone_count_left != 1 || current.zone_count_right != 1
0375                 || next.zone_count_left != 1 || next.zone_count_right != 1)
0376             {
0377                 // The rank should be the same
0378                 // It should have one zone on either side
0379                 continue;
0380             }
0381 
0382             if (current.is_shifted || next.is_shifted) {
0383                 // The opposite point is shifted. Therefore a spike measurement
0384                 // cannot be done.
0385                 continue;
0386             }
0387 
0388             // Precise measurement, not from the turn, but over the whole intersecting segment.
0389             // If it is positive (on the left side), it is a spike.
0390             auto const dm = get_distance_measure(current.opposite_point, current.point, next.point,
0391                 m_intersection_strategy);
0392             if (dm.measure <= 0)
0393             {
0394                 continue;
0395             }
0396 
0397             // There is a small measurable difference.
0398             // Make the cluster open and adapt the counts.
0399             cluster.open_count++;
0400             current.zone_count_left = 0;
0401             next.zone_count_right = 0;
0402         }
0403     }
0404 
0405     void assign_turn_operations(cluster_info const& cluster,
0406         connection_map_type const& connection_map)
0407     {
0408         // Assign the items, per seg_id, back to the outgoing turn operations.
0409         for (std::size_t index : cluster.turn_indices)
0410         {
0411             auto& turn = m_turns[index];
0412             for (int i = 0; i < 2; i++)
0413             {
0414                 auto& op = turn.operations[i];
0415                 connection_key const key{op.seg_id, connection_type::outgoing};
0416                 auto const it = connection_map.find(key);
0417                 if (it != connection_map.end())
0418                 {
0419                     op.enriched.count_left = it->second.zone_count_left;
0420                     op.enriched.count_right = it->second.zone_count_right;
0421                     op.enriched.rank = it->second.rank;
0422                 }
0423             }
0424         }
0425     }
0426 
0427     // Currently necessary for some failing cases in buffer only, where due to floating point
0428     // precision the i/u turns get unexpected counts for left/right.
0429     // rt_w10, rt_w11, rt_w14, rt_w15
0430     // The original sides are measured over the two whole intersecting segments.
0431     // The sides in clusters are measured w.r.t. the turn point, which is the point of the first cluster.
0432     // This can differ.
0433     // It should be possible to fix it in another way.
0434     void change_reversed_operations(signed_size_type const cluster_id, cluster_info const& cluster,
0435             point_type const& point_turn, point_type const& point_origin)
0436     {
0437         std::set<std::size_t> reversed_indices;
0438         for (std::size_t index : cluster.turn_indices)
0439         {
0440             auto const& turn = m_turns[index];
0441             if (! turn.combination(operation_union, operation_intersection))
0442             {
0443                 continue;
0444             }
0445             int const union_index = turn.operations[0].operation == operation_union ? 0 : 1;
0446             auto const& op_u = turn.operations[union_index];
0447             auto const& op_i = turn.operations[1 - union_index];
0448             if (op_u.enriched.count_left > 0 && op_i.enriched.count_left == 0)
0449             {
0450                 reversed_indices.insert(index);
0451             }
0452         }
0453 
0454         if (reversed_indices.empty())
0455         {
0456             return;
0457         }
0458 
0459 #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH)
0460         std::cout << " *** REVERSED OPERATIONS in cluster: " << cluster_id
0461             << " cluster size: " << cluster.turn_indices.size()
0462             << " reversed: " << reversed_indices.size()
0463             << std::endl;
0464 #endif
0465         for (std::size_t index : cluster.turn_indices)
0466         {
0467             auto& turn = m_turns[index];
0468             auto& op0 = turn.operations[0];
0469             auto& op1 = turn.operations[1];
0470 
0471             bool const is_same_target = op0.enriched.travels_to_ip_index == op1.enriched.travels_to_ip_index;
0472             if (is_same_target && reversed_indices.find(index) != reversed_indices.end())
0473             {
0474                 // Best choice: i/u are nearly collinear, so we can let them continue.
0475                 op0.operation = operation_continue;
0476                 op1.operation = operation_continue;
0477 
0478                 // Also adapt the left/right-counts, both should get the minimum of both.
0479                 op0.enriched.count_left = (std::min)(op0.enriched.count_left, op1.enriched.count_left);
0480                 op1.enriched.count_left = op0.enriched.count_left;
0481                 op0.enriched.count_right = (std::min)(op0.enriched.count_right, op1.enriched.count_right);
0482                 op1.enriched.count_right = op0.enriched.count_right;
0483             }
0484         }
0485     }
0486 
0487     template <typename Visitor>
0488     void apply(signed_size_type const cluster_id, cluster_info& cluster, Visitor& visitor)
0489     {
0490         if (cluster.turn_indices.empty())
0491         {
0492             // Defensive check.
0493             return;
0494         }
0495 
0496         point_type const& point_turn = m_turns[*cluster.turn_indices.begin()].point;
0497         point_type point_origin;
0498         connection_map_type connection_map;
0499         get_connection_map(cluster, point_turn, connection_map, point_origin);
0500 
0501         // Sort the items by position code, and if equal, by side.
0502         // For this they are copied into a vector.
0503         std::vector<connection_item<point_type>> item_vector;
0504         for (auto const& key_value : connection_map)
0505         {
0506             connection_item<point_type> item;
0507             item.key = key_value.first;
0508             item.properties = key_value.second;
0509             item_vector.push_back(std::move(item));
0510         }
0511 
0512         sort(point_turn, item_vector);
0513         assign_ranks(point_turn, item_vector);
0514 
0515         auto const rank_size = item_vector.back().properties.rank + 1;
0516         auto const zone_counts = get_zone_counts(item_vector, rank_size);
0517 
0518         assign_zone_counts(item_vector, zone_counts, rank_size);
0519 
0520         cluster.open_count = get_open_count(zone_counts, rank_size);
0521         if (cluster.open_count == 0)
0522         {
0523             handle_spikes(cluster, item_vector);
0524         }
0525 
0526         // Assign the updated properties back to the connection map
0527         for (auto const& item : item_vector)
0528         {
0529             connection_map[item.key] = item.properties;
0530         }
0531 
0532         assign_turn_operations(cluster, connection_map);
0533         change_reversed_operations(cluster_id, cluster, point_turn, point_origin);
0534 
0535 #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSE_GRAPH)
0536         // List the connections
0537         std::cout << "Cluster " << cluster_id << " size: " << cluster.turn_indices.size() << std::endl;
0538         for (auto const& item : item_vector)
0539         {
0540             std::cout << "  " << item.key.seg_id
0541                 << " " << (item.key.connection == connection_type::incoming ? " in" : "out")
0542                 << " " << item.properties.position_code
0543                 << " " << item.properties.rank
0544                 << " " << item.properties.zone_count_left
0545                 << " " << item.properties.zone_count_right
0546                 << std::endl;
0547         }
0548 #endif
0549 
0550         visitor.visit_cluster_connections(cluster_id, m_turns, cluster, item_vector);
0551     }
0552 
0553     template <typename Visitor>
0554     void apply(Visitor& visitor)
0555     {
0556         for (auto& key_value : m_clusters)
0557         {
0558             auto& cluster = key_value.second;
0559             if (cluster.turn_indices.empty())
0560             {
0561                 continue;
0562             }
0563 
0564             apply(key_value.first, cluster, visitor);
0565         }
0566     }
0567 
0568 private:
0569     Geometry1 const& m_geometry1;
0570     Geometry2 const& m_geometry2;
0571     Turns& m_turns;
0572     Clusters& m_clusters;
0573     Strategy const& m_intersection_strategy;
0574     decltype(m_intersection_strategy.side()) m_side_strategy;
0575 };
0576 
0577 }} // namespace detail::overlay
0578 #endif //DOXYGEN_NO_DETAIL
0579 
0580 }} // namespace boost::geometry
0581 
0582 #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_CLUSTER_INFO_HPP