|
14 | 14 | #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DISCARD_DUPLICATE_TURNS_HPP
|
15 | 15 |
|
16 | 16 | #include <map>
|
| 17 | +#include <set> |
17 | 18 |
|
18 | 19 | #include <boost/geometry/algorithms/detail/overlay/turn_info.hpp>
|
19 | 20 | #include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
|
| 21 | +#include <boost/geometry/views/enumerate_view.hpp> |
20 | 22 |
|
21 | 23 | namespace boost { namespace geometry
|
22 | 24 | {
|
@@ -88,72 +90,159 @@ inline bool corresponding_turn(Turn const& turn, Turn const& start_turn,
|
88 | 90 | return count == 2;
|
89 | 91 | }
|
90 | 92 |
|
| 93 | +// Verify turns (other than start, and cross) if they are present in the map, and if so, |
| 94 | +// if they have the other turns as corresponding, discard the start turn. |
| 95 | +template <typename Turns, typename TurnBySegmentMap, typename Geometry0, typename Geometry1> |
| 96 | +void discard_duplicate_start_turns(Turns& turns, |
| 97 | + TurnBySegmentMap const& start_turns_by_segment, |
| 98 | + Geometry0 const& geometry0, |
| 99 | + Geometry1 const& geometry1) |
| 100 | +{ |
| 101 | + using multi_and_ring_id_type = std::pair<signed_size_type, signed_size_type>; |
| 102 | + auto adapt_id = [](segment_identifier const& seg_id) |
| 103 | + { |
| 104 | + return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index}; |
| 105 | + }; |
| 106 | + |
| 107 | + for (auto& turn : turns) |
| 108 | + { |
| 109 | + // Any turn which "crosses" does not have a corresponding turn. |
| 110 | + // Also avoid comparing "start" with itself |
| 111 | + if (turn.method == method_crosses || turn.method == method_start) |
| 112 | + { |
| 113 | + continue; |
| 114 | + } |
| 115 | + bool const is_touch = turn.method == method_touch; |
| 116 | + for (auto const& op : turn.operations) |
| 117 | + { |
| 118 | + auto it = start_turns_by_segment.find(adapt_id(op.seg_id)); |
| 119 | + if (it == start_turns_by_segment.end()) |
| 120 | + { |
| 121 | + continue; |
| 122 | + } |
| 123 | + for (std::size_t const& i : it->second) |
| 124 | + { |
| 125 | + auto& start_turn = turns[i]; |
| 126 | + if (start_turn.cluster_id == turn.cluster_id |
| 127 | + && corresponding_turn(turn, start_turn, geometry0, geometry1)) |
| 128 | + { |
| 129 | + // Discard the start turn, unless there is a touch before. |
| 130 | + // In that case the start is used and the touch is discarded. |
| 131 | + (is_touch ? turn : start_turn).discarded = true; |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +// Discard turns for the following (rare) case: |
| 139 | +// - they are consecutive |
| 140 | +// - the first has a touch, the second a touch_interior |
| 141 | +// And then one of the segments touches the others next in the middle. |
| 142 | +// This is geometrically not possible, and caused by floating point precision. |
| 143 | +// Discard the second (touch interior) |
| 144 | +template <typename Turns, typename Geometry0, typename Geometry1> |
| 145 | +void discard_touch_touch_interior_turns(Turns& turns, |
| 146 | + Geometry0 const& geometry0, |
| 147 | + Geometry1 const& geometry1) |
| 148 | +{ |
| 149 | + for (auto& current_turn : turns) |
| 150 | + { |
| 151 | + if (current_turn.method != method_touch_interior) |
| 152 | + { |
| 153 | + // Because touch_interior is a rarer case, it is more efficient to start with that |
| 154 | + continue; |
| 155 | + } |
| 156 | + for (auto const& previous_turn : turns) |
| 157 | + { |
| 158 | + if (previous_turn.method != method_touch) |
| 159 | + { |
| 160 | + continue; |
| 161 | + } |
| 162 | + |
| 163 | + // Compare 0 with 0 and 1 with 1 |
| 164 | + // Note that 0 has always source 0 and 1 has always source 1 |
| 165 | + // (not in buffer). Therefore this comparison is OK. |
| 166 | + // MAYBE we need to check for buffer. |
| 167 | + bool const common0 = current_turn.operations[0].seg_id == previous_turn.operations[0].seg_id; |
| 168 | + bool const common1 = current_turn.operations[1].seg_id == previous_turn.operations[1].seg_id; |
| 169 | + |
| 170 | + // If one of the operations is common, and the other is not, then there is one comment segment. |
| 171 | + bool const has_common_segment = common0 != common1; |
| 172 | + |
| 173 | + if (! has_common_segment) |
| 174 | + { |
| 175 | + continue; |
| 176 | + } |
| 177 | + |
| 178 | + // If the second index (1) is common, we need to check consecutivity of the first index (0) |
| 179 | + // and vice versa. |
| 180 | + bool const consecutive = |
| 181 | + common1 ? is_consecutive(previous_turn.operations[0].seg_id, current_turn.operations[0].seg_id, geometry0, geometry1) |
| 182 | + : is_consecutive(previous_turn.operations[1].seg_id, current_turn.operations[1].seg_id, geometry0, geometry1); |
| 183 | + |
| 184 | + if (consecutive) |
| 185 | + { |
| 186 | + current_turn.discarded = true; |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | +} |
| 191 | + |
91 | 192 | template <typename Turns, typename Geometry0, typename Geometry1>
|
92 |
| -inline void discard_duplicate_start_turns(Turns& turns, |
93 |
| - Geometry0 const& geometry0, |
94 |
| - Geometry1 const& geometry1) |
| 193 | +void discard_duplicate_turns(Turns& turns, |
| 194 | + Geometry0 const& geometry0, |
| 195 | + Geometry1 const& geometry1) |
95 | 196 | {
|
96 | 197 | // Start turns are generated, in case the previous turn is missed.
|
97 | 198 | // But often it is not missed, and then it should be deleted.
|
98 | 199 | // This is how it can be
|
99 | 200 | // (in float, collinear, points far apart due to floating point precision)
|
100 | 201 | // [m, i s:0, v:6 1/1 (1) // u s:1, v:5 pnt (2.54044, 3.12623)]
|
101 | 202 | // [s, i s:0, v:7 0/1 (0) // u s:1, v:5 pnt (2.70711, 3.29289)]
|
| 203 | + // |
| 204 | + // Also, if two turns are consecutive, and one is touch and the other touch_interior, |
| 205 | + // the touch_interior is discarded. |
102 | 206 |
|
103 | 207 | using multi_and_ring_id_type = std::pair<signed_size_type, signed_size_type>;
|
104 | 208 |
|
105 |
| - auto adapt_id = [](segment_identifier const& seg_id) |
| 209 | + auto add_to_map = [](auto const& turn, auto& map, std::size_t index) |
106 | 210 | {
|
107 |
| - return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index}; |
| 211 | + auto adapt_id = [](segment_identifier const& seg_id) |
| 212 | + { |
| 213 | + return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index}; |
| 214 | + }; |
| 215 | + for (auto const& op : turn.operations) |
| 216 | + { |
| 217 | + map[adapt_id(op.seg_id)].insert(index); |
| 218 | + } |
108 | 219 | };
|
109 | 220 |
|
110 |
| - // 1 Build map of start turns (multi/ring-id -> turn indices) |
111 |
| - std::map<multi_and_ring_id_type, std::vector<std::size_t>> start_turns_per_segment; |
112 |
| - std::size_t index = 0; |
113 |
| - for (auto const& turn : turns) |
| 221 | + // Build map of start turns (multi/ring-id -> turn indices) |
| 222 | + // and count touch and touch_interior turns (to verify if later checks are needed) |
| 223 | + std::map<multi_and_ring_id_type, std::set<std::size_t>> start_turns_by_segment; |
| 224 | + std::size_t touch_count = 0; |
| 225 | + std::size_t touch_interior_count = 0; |
| 226 | + for (auto const& item : util::enumerate(turns)) |
114 | 227 | {
|
115 |
| - if (turn.method == method_start) |
| 228 | + auto const& turn = item.value; |
| 229 | + switch(turn.method) |
116 | 230 | {
|
117 |
| - for (auto const& op : turn.operations) |
118 |
| - { |
119 |
| - start_turns_per_segment[adapt_id(op.seg_id)].push_back(index); |
120 |
| - } |
| 231 | + case method_start: add_to_map(turn, start_turns_by_segment, item.index); break; |
| 232 | + case method_touch: touch_count++; break; |
| 233 | + case method_touch_interior: touch_interior_count++; break; |
| 234 | + default: break; |
121 | 235 | }
|
122 |
| - index++; |
123 | 236 | }
|
124 | 237 |
|
125 |
| - // 2: Verify all other turns if they are present in the map, and if so, |
126 |
| - // if they have the other turns as corresponding |
127 |
| - for (auto const& turn : turns) |
| 238 | + if (!start_turns_by_segment.empty()) |
128 | 239 | {
|
129 |
| - // Any turn which "crosses" does not have a corresponding turn. |
130 |
| - // Also avoid comparing "start" with itself. |
131 |
| - if (turn.method != method_crosses && turn.method != method_start) |
132 |
| - { |
133 |
| - for (auto const& op : turn.operations) |
134 |
| - { |
135 |
| - auto it = start_turns_per_segment.find(adapt_id(op.seg_id)); |
136 |
| - if (it != start_turns_per_segment.end()) |
137 |
| - { |
138 |
| - for (std::size_t const& i : it->second) |
139 |
| - { |
140 |
| - if (turns[i].cluster_id != turn.cluster_id) |
141 |
| - { |
142 |
| - // The turns are not part of the same cluster, |
143 |
| - // or one is clustered and the other is not. |
144 |
| - // This is not corresponding. |
145 |
| - continue; |
146 |
| - } |
147 |
| - if (corresponding_turn(turn, turns[i], |
148 |
| - geometry0, geometry1)) |
149 |
| - { |
150 |
| - turns[i].discarded = true; |
151 |
| - } |
152 |
| - } |
153 |
| - } |
154 |
| - } |
155 |
| - } |
156 |
| - index++; |
| 240 | + discard_duplicate_start_turns(turns, start_turns_by_segment, geometry0, geometry1); |
| 241 | + } |
| 242 | + |
| 243 | + if (touch_count > 0 && touch_interior_count > 0) |
| 244 | + { |
| 245 | + discard_touch_touch_interior_turns(turns, geometry0, geometry1); |
157 | 246 | }
|
158 | 247 | }
|
159 | 248 |
|
|
0 commit comments