1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 
3 // Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
4 // Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland.
5 
6 // This file was modified by Oracle on 2013, 2014, 2015, 2017.
7 // Modifications copyright (c) 2013-2017 Oracle and/or its affiliates.
8 
9 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
10 
11 // Use, modification and distribution is subject to the Boost Software License,
12 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
13 // http://www.boost.org/LICENSE_1_0.txt)
14 
15 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP
16 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP
17 
18 #include <boost/throw_exception.hpp>
19 
20 #include <boost/geometry/core/assert.hpp>
21 
22 #include <boost/geometry/util/condition.hpp>
23 
24 #include <boost/geometry/algorithms/detail/overlay/get_turn_info.hpp>
25 #include <boost/geometry/algorithms/detail/overlay/get_turn_info_for_endpoint.hpp>
26 
27 // TEMP, for spikes detector
28 //#include <boost/geometry/algorithms/detail/overlay/get_turn_info_ll.hpp>
29 
30 namespace boost { namespace geometry {
31 
32 #ifndef DOXYGEN_NO_DETAIL
33 namespace detail { namespace overlay {
34 
35 template<typename AssignPolicy>
36 struct get_turn_info_linear_areal
37 {
38     // Currently only Linear spikes are handled
39     // Areal spikes are ignored
40     static const bool handle_spikes = true;
41 
42     template
43     <
44         typename Point1,
45         typename Point2,
46         typename TurnInfo,
47         typename IntersectionStrategy,
48         typename RobustPolicy,
49         typename OutputIterator
50     >
applyboost::geometry::detail::overlay::get_turn_info_linear_areal51     static inline OutputIterator apply(
52                 Point1 const& pi, Point1 const& pj, Point1 const& pk,
53                 Point2 const& qi, Point2 const& qj, Point2 const& qk,
54                 bool is_p_first, bool is_p_last,
55                 bool is_q_first, bool is_q_last,
56                 TurnInfo const& tp_model,
57                 IntersectionStrategy const& intersection_strategy,
58                 RobustPolicy const& robust_policy,
59                 OutputIterator out)
60     {
61         typedef intersection_info
62             <
63                 Point1, Point2,
64                 typename TurnInfo::point_type,
65                 IntersectionStrategy,
66                 RobustPolicy
67             > inters_info;
68 
69         inters_info inters(pi, pj, pk, qi, qj, qk, intersection_strategy, robust_policy);
70 
71         char const method = inters.d_info().how;
72 
73         // Copy, to copy possibly extended fields
74         TurnInfo tp = tp_model;
75 
76         // Select method and apply
77         switch(method)
78         {
79             case 'a' : // collinear, "at"
80             case 'f' : // collinear, "from"
81             case 's' : // starts from the middle
82                 get_turn_info_for_endpoint<true, true>(
83                     pi, pj, pk, qi, qj, qk,
84                     is_p_first, is_p_last, is_q_first, is_q_last,
85                     tp_model, inters, method_none, out);
86                 break;
87 
88             case 'd' : // disjoint: never do anything
89                 break;
90 
91             case 'm' :
92             {
93                 if ( get_turn_info_for_endpoint<false, true>(
94                         pi, pj, pk, qi, qj, qk,
95                         is_p_first, is_p_last, is_q_first, is_q_last,
96                         tp_model, inters, method_touch_interior, out) )
97                 {
98                     // do nothing
99                 }
100                 else
101                 {
102                     typedef touch_interior
103                         <
104                             TurnInfo
105                         > policy;
106 
107                     // If Q (1) arrives (1)
108                     if ( inters.d_info().arrival[1] == 1 )
109                     {
110                         policy::template apply<0>(pi, pj, pk, qi, qj, qk,
111                                     tp, inters.i_info(), inters.d_info(),
112                                     inters.sides());
113                     }
114                     else
115                     {
116                         // Swap p/q
117                         side_calculator
118                             <
119                                 typename inters_info::cs_tag,
120                                 typename inters_info::robust_point2_type,
121                                 typename inters_info::robust_point1_type,
122                                 typename inters_info::side_strategy_type
123                             > swapped_side_calc(inters.rqi(), inters.rqj(), inters.rqk(),
124                                                 inters.rpi(), inters.rpj(), inters.rpk(),
125                                                 inters.get_side_strategy());
126                         policy::template apply<1>(qi, qj, qk, pi, pj, pk,
127                                     tp, inters.i_info(), inters.d_info(),
128                                     swapped_side_calc);
129                     }
130 
131                     if ( tp.operations[1].operation == operation_blocked )
132                     {
133                         tp.operations[0].is_collinear = true;
134                     }
135 
136                     replace_method_and_operations_tm(tp.method,
137                                                      tp.operations[0].operation,
138                                                      tp.operations[1].operation);
139 
140                     // this function assumes that 'u' must be set for a spike
141                     calculate_spike_operation(tp.operations[0].operation,
142                                               inters, is_p_last);
143 
144                     AssignPolicy::apply(tp, pi, qi, inters);
145 
146                     *out++ = tp;
147                 }
148             }
149             break;
150             case 'i' :
151             {
152                 crosses<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
153                                          tp, inters.i_info(), inters.d_info());
154 
155                 replace_operations_i(tp.operations[0].operation, tp.operations[1].operation);
156 
157                 AssignPolicy::apply(tp, pi, qi, inters);
158                 *out++ = tp;
159             }
160             break;
161             case 't' :
162             {
163                 // Both touch (both arrive there)
164                 if ( get_turn_info_for_endpoint<false, true>(
165                         pi, pj, pk, qi, qj, qk,
166                         is_p_first, is_p_last, is_q_first, is_q_last,
167                         tp_model, inters, method_touch, out) )
168                 {
169                     // do nothing
170                 }
171                 else
172                 {
173                     touch<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
174                             tp, inters.i_info(), inters.d_info(), inters.sides());
175 
176                     if ( tp.operations[1].operation == operation_blocked )
177                     {
178                         tp.operations[0].is_collinear = true;
179                     }
180 
181                     // workarounds for touch<> not taking spikes into account starts here
182                     // those was discovered empirically
183                     // touch<> is not symmetrical!
184                     // P spikes and Q spikes may produce various operations!
185                     // Only P spikes are valid for L/A
186                     // TODO: this is not optimal solution - think about rewriting touch<>
187 
188                     if ( tp.operations[0].operation == operation_blocked )
189                     {
190                         // a spike on P on the same line with Q1
191                         if ( inters.is_spike_p() )
192                         {
193                             if ( inters.sides().qk_wrt_p1() == 0 )
194                             {
195                                 tp.operations[0].is_collinear = true;
196                             }
197                             else
198                             {
199                                 tp.operations[0].operation = operation_union;
200                             }
201                         }
202                     }
203                     else if ( tp.operations[0].operation == operation_continue
204                            && tp.operations[1].operation == operation_continue )
205                     {
206                         // P spike on the same line with Q2 (opposite)
207                         if ( inters.sides().pk_wrt_q1() == -inters.sides().qk_wrt_q1()
208                           && inters.is_spike_p() )
209                         {
210                             tp.operations[0].operation = operation_union;
211                             tp.operations[1].operation = operation_union;
212                         }
213                     }
214                     else if ( tp.operations[0].operation == operation_none
215                            && tp.operations[1].operation == operation_none )
216                     {
217                         // spike not handled by touch<>
218                         if ( inters.is_spike_p() )
219                         {
220                             tp.operations[0].operation = operation_intersection;
221                             tp.operations[1].operation = operation_union;
222 
223                             if ( inters.sides().pk_wrt_q2() == 0 )
224                             {
225                                 tp.operations[0].operation = operation_continue; // will be converted to i
226                                 tp.operations[0].is_collinear = true;
227                             }
228                         }
229                     }
230 
231                     // workarounds for touch<> not taking spikes into account ends here
232 
233                     replace_method_and_operations_tm(tp.method,
234                                                      tp.operations[0].operation,
235                                                      tp.operations[1].operation);
236 
237                     bool ignore_spike
238                         = calculate_spike_operation(tp.operations[0].operation,
239                                                     inters, is_p_last);
240 
241 // TODO: move this into the append_xxx and call for each turn?
242                     AssignPolicy::apply(tp, pi, qi, inters);
243 
244                     if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
245                       || ignore_spike
246                       || ! append_opposite_spikes<append_touches>( // for 'i' or 'c' i???
247                                 tp, inters, is_p_last, is_q_last, out) )
248                     {
249                         *out++ = tp;
250                     }
251                 }
252             }
253             break;
254             case 'e':
255             {
256                 if ( get_turn_info_for_endpoint<true, true>(
257                         pi, pj, pk, qi, qj, qk,
258                         is_p_first, is_p_last, is_q_first, is_q_last,
259                         tp_model, inters, method_equal, out) )
260                 {
261                     // do nothing
262                 }
263                 else
264                 {
265                     tp.operations[0].is_collinear = true;
266 
267                     if ( ! inters.d_info().opposite )
268                     {
269                         // Both equal
270                         // or collinear-and-ending at intersection point
271                         equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
272                             tp, inters.i_info(), inters.d_info(), inters.sides());
273 
274                         turn_transformer_ec<false> transformer(method_touch);
275                         transformer(tp);
276 
277 // TODO: move this into the append_xxx and call for each turn?
278                         AssignPolicy::apply(tp, pi, qi, inters);
279 
280                         // conditionally handle spikes
281                         if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
282                           || ! append_collinear_spikes(tp, inters, is_p_last, is_q_last,
283                                                        method_touch, append_equal, out) )
284                         {
285                             *out++ = tp; // no spikes
286                         }
287                     }
288                     else
289                     {
290                         equal_opposite
291                             <
292                                 TurnInfo,
293                                 AssignPolicy
294                             >::apply(pi, qi,
295                                      tp, out, inters);
296                     }
297                 }
298             }
299             break;
300             case 'c' :
301             {
302                 // Collinear
303                 if ( get_turn_info_for_endpoint<true, true>(
304                         pi, pj, pk, qi, qj, qk,
305                         is_p_first, is_p_last, is_q_first, is_q_last,
306                         tp_model, inters, method_collinear, out) )
307                 {
308                     // do nothing
309                 }
310                 else
311                 {
312                     tp.operations[0].is_collinear = true;
313 
314                     if ( ! inters.d_info().opposite )
315                     {
316                         method_type method_replace = method_touch_interior;
317                         append_version_c version = append_collinear;
318 
319                         if ( inters.d_info().arrival[0] == 0 )
320                         {
321                             // Collinear, but similar thus handled as equal
322                             equal<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
323                                     tp, inters.i_info(), inters.d_info(), inters.sides());
324 
325                             method_replace = method_touch;
326                             version = append_equal;
327                         }
328                         else
329                         {
330                             collinear<TurnInfo>::apply(pi, pj, pk, qi, qj, qk,
331                                     tp, inters.i_info(), inters.d_info(), inters.sides());
332 
333                             //method_replace = method_touch_interior;
334                             //version = append_collinear;
335                         }
336 
337                         turn_transformer_ec<false> transformer(method_replace);
338                         transformer(tp);
339 
340 // TODO: move this into the append_xxx and call for each turn?
341                         AssignPolicy::apply(tp, pi, qi, inters);
342 
343                         // conditionally handle spikes
344                         if ( ! BOOST_GEOMETRY_CONDITION(handle_spikes)
345                           || ! append_collinear_spikes(tp, inters, is_p_last, is_q_last,
346                                                        method_replace, version, out) )
347                         {
348                             // no spikes
349                             *out++ = tp;
350                         }
351                     }
352                     else
353                     {
354                         // Is this always 'm' ?
355                         turn_transformer_ec<false> transformer(method_touch_interior);
356 
357                         // conditionally handle spikes
358                         if ( BOOST_GEOMETRY_CONDITION(handle_spikes) )
359                         {
360                             append_opposite_spikes<append_collinear_opposite>(
361                                     tp, inters, is_p_last, is_q_last, out);
362                         }
363 
364                         // TODO: ignore for spikes?
365                         //       E.g. pass is_p_valid = !is_p_last && !is_pj_spike,
366                         //       the same with is_q_valid
367 
368                         collinear_opposite
369                             <
370                                 TurnInfo,
371                                 AssignPolicy
372                             >::apply(pi, pj, pk, qi, qj, qk,
373                                 tp, out, inters,
374                                 inters.sides(), transformer,
375                                 !is_p_last, true); // qk is always valid
376                     }
377                 }
378             }
379             break;
380             case '0' :
381             {
382                 // degenerate points
383                 if ( BOOST_GEOMETRY_CONDITION(AssignPolicy::include_degenerate) )
384                 {
385                     only_convert::apply(tp, inters.i_info());
386 
387                     if ( is_p_first
388                       && equals::equals_point_point(pi, tp.point) )
389                     {
390                         tp.operations[0].position = position_front;
391                     }
392                     else if ( is_p_last
393                            && equals::equals_point_point(pj, tp.point) )
394                     {
395                         tp.operations[0].position = position_back;
396                     }
397                     // tp.operations[1].position = position_middle;
398 
399                     AssignPolicy::apply(tp, pi, qi, inters);
400                     *out++ = tp;
401                 }
402             }
403             break;
404             default :
405             {
406 #if defined(BOOST_GEOMETRY_DEBUG_ROBUSTNESS)
407                 std::cout << "TURN: Unknown method: " << method << std::endl;
408 #endif
409 #if ! defined(BOOST_GEOMETRY_OVERLAY_NO_THROW)
410                 BOOST_THROW_EXCEPTION(turn_info_exception(method));
411 #endif
412             }
413             break;
414         }
415 
416         return out;
417     }
418 
419     template <typename Operation,
420               typename IntersectionInfo>
calculate_spike_operationboost::geometry::detail::overlay::get_turn_info_linear_areal421     static inline bool calculate_spike_operation(Operation & op,
422                                                  IntersectionInfo const& inters,
423                                                  bool is_p_last)
424     {
425         bool is_p_spike = ( op == operation_union || op == operation_intersection )
426                        && ! is_p_last
427                        && inters.is_spike_p();
428 
429         if ( is_p_spike )
430         {
431             int const pk_q1 = inters.sides().pk_wrt_q1();
432 
433             bool going_in = pk_q1 < 0; // Pk on the right
434             bool going_out = pk_q1 > 0; // Pk on the left
435 
436             int const qk_q1 = inters.sides().qk_wrt_q1();
437 
438             // special cases
439             if ( qk_q1 < 0 ) // Q turning R
440             {
441                 // spike on the edge point
442                 // if it's already known that the spike is going out this musn't be checked
443                 if ( ! going_out
444                   && equals::equals_point_point(inters.rpj(), inters.rqj()) )
445                 {
446                     int const pk_q2 = inters.sides().pk_wrt_q2();
447                     going_in = pk_q1 < 0 && pk_q2 < 0; // Pk on the right of both
448                     going_out = pk_q1 > 0 || pk_q2 > 0; // Pk on the left of one of them
449                 }
450             }
451             else if ( qk_q1 > 0 ) // Q turning L
452             {
453                 // spike on the edge point
454                 // if it's already known that the spike is going in this musn't be checked
455                 if ( ! going_in
456                   && equals::equals_point_point(inters.rpj(), inters.rqj()) )
457                 {
458                     int const pk_q2 = inters.sides().pk_wrt_q2();
459                     going_in = pk_q1 < 0 || pk_q2 < 0; // Pk on the right of one of them
460                     going_out = pk_q1 > 0 && pk_q2 > 0; // Pk on the left of both
461                 }
462             }
463 
464             if ( going_in )
465             {
466                 op = operation_intersection;
467                 return true;
468             }
469             else if ( going_out )
470             {
471                 op = operation_union;
472                 return true;
473             }
474         }
475 
476         return false;
477     }
478 
479     enum append_version_c { append_equal, append_collinear };
480 
481     template <typename TurnInfo,
482               typename IntersectionInfo,
483               typename OutIt>
append_collinear_spikesboost::geometry::detail::overlay::get_turn_info_linear_areal484     static inline bool append_collinear_spikes(TurnInfo & tp,
485                                                IntersectionInfo const& inters,
486                                                bool is_p_last, bool /*is_q_last*/,
487                                                method_type method, append_version_c version,
488                                                OutIt out)
489     {
490         // method == touch || touch_interior
491         // both position == middle
492 
493         bool is_p_spike = ( version == append_equal ?
494                             ( tp.operations[0].operation == operation_union
495                            || tp.operations[0].operation == operation_intersection ) :
496                             tp.operations[0].operation == operation_continue )
497                        && ! is_p_last
498                        && inters.is_spike_p();
499 
500         // TODO: throw an exception for spike in Areal?
501         /*bool is_q_spike = tp.operations[1].operation == operation_continue
502                        && inters.is_spike_q();
503 
504         // both are collinear spikes on the same IP, we can just follow both
505         if ( is_p_spike && is_q_spike )
506         {
507             return false;
508         }
509         // spike on Linear - it's turning back on the boundary of Areal
510         else*/
511         if ( is_p_spike )
512         {
513             tp.method = method;
514             tp.operations[0].operation = operation_blocked;
515             tp.operations[1].operation = operation_union;
516             *out++ = tp;
517             tp.operations[0].operation = operation_continue; // boundary
518             //tp.operations[1].operation = operation_union;
519             *out++ = tp;
520 
521             return true;
522         }
523         // spike on Areal - Linear is going outside
524         /*else if ( is_q_spike )
525         {
526             tp.method = method;
527             tp.operations[0].operation = operation_union;
528             tp.operations[1].operation = operation_continue;
529             *out++ = tp;
530             *out++ = tp;
531 
532             return true;
533         }*/
534 
535         return false;
536     }
537 
538     enum append_version_o { append_touches, append_collinear_opposite };
539 
540     template <append_version_o Version,
541               typename TurnInfo,
542               typename IntersectionInfo,
543               typename OutIt>
append_opposite_spikesboost::geometry::detail::overlay::get_turn_info_linear_areal544     static inline bool append_opposite_spikes(TurnInfo & tp,
545                                               IntersectionInfo const& inters,
546                                               bool is_p_last, bool /*is_q_last*/,
547                                               OutIt out)
548     {
549         static const bool is_version_touches = (Version == append_touches);
550 
551         bool is_p_spike = ( is_version_touches ?
552                             ( tp.operations[0].operation == operation_continue
553                            || tp.operations[0].operation == operation_intersection ) : // i ???
554                             true )
555                        && ! is_p_last
556                        && inters.is_spike_p();
557 
558         // TODO: throw an exception for spike in Areal?
559         /*bool is_q_spike = ( ( Version == append_touches
560                            && tp.operations[1].operation == operation_continue )
561                          || ( Version == append_collinear_opposite
562                            && tp.operations[1].operation == operation_none ) )
563                        && inters.is_spike_q();
564 
565         if ( is_p_spike && is_q_spike )
566         {
567             // u/u or nothing?
568             return false;
569         }
570         else*/
571         if ( is_p_spike )
572         {
573             if ( BOOST_GEOMETRY_CONDITION(is_version_touches)
574               || inters.d_info().arrival[0] == 1 )
575             {
576                 if ( BOOST_GEOMETRY_CONDITION(is_version_touches) )
577                 {
578                     tp.operations[0].is_collinear = true;
579                     //tp.operations[1].is_collinear = false;
580                     tp.method = method_touch;
581                 }
582                 else
583                 {
584                     tp.operations[0].is_collinear = true;
585                     //tp.operations[1].is_collinear = false;
586 
587                     BOOST_GEOMETRY_ASSERT(inters.i_info().count > 1);
588                     base_turn_handler::assign_point(tp, method_touch_interior, inters.i_info(), 1);
589 
590                     AssignPolicy::apply(tp, inters.pi(), inters.qi(), inters);
591                 }
592 
593                 tp.operations[0].operation = operation_blocked;
594                 tp.operations[1].operation = operation_continue; // boundary
595                 *out++ = tp;
596                 tp.operations[0].operation = operation_continue; // boundary
597                 //tp.operations[1].operation = operation_continue; // boundary
598                 *out++ = tp;
599 
600                 return true;
601             }
602         }
603         /*else if ( is_q_spike )
604         {
605             tp.operations[0].is_collinear = true;
606             tp.method = is_version_touches ? method_touch : method_touch_interior;
607             tp.operations[0].operation = operation_continue;
608             tp.operations[1].operation = operation_continue; // boundary
609             *out++ = tp;
610             *out++ = tp;
611 
612             return true;
613         }*/
614 
615         return false;
616     }
617 
replace_method_and_operations_tmboost::geometry::detail::overlay::get_turn_info_linear_areal618     static inline void replace_method_and_operations_tm(method_type & method,
619                                                         operation_type & op0,
620                                                         operation_type & op1)
621     {
622         if ( op0 == operation_blocked && op1 == operation_blocked )
623         {
624             // NOTE: probably only if methods are WRT IPs, not segments!
625             method = (method == method_touch ? method_equal : method_collinear);
626         }
627 
628         // Assuming G1 is always Linear
629         if ( op0 == operation_blocked )
630         {
631             op0 = operation_continue;
632         }
633 
634         if ( op1 == operation_blocked )
635         {
636             op1 = operation_continue;
637         }
638         else if ( op1 == operation_intersection )
639         {
640             op1 = operation_union;
641         }
642 
643         // spikes in 'm'
644         if ( method == method_error )
645         {
646             method = method_touch_interior;
647             op0 = operation_union;
648             op1 = operation_union;
649         }
650     }
651 
652     template <bool IsFront>
653     class turn_transformer_ec
654     {
655     public:
turn_transformer_ec(method_type method_t_or_m)656         explicit turn_transformer_ec(method_type method_t_or_m)
657             : m_method(method_t_or_m)
658         {}
659 
660         template <typename Turn>
operator ()(Turn & turn) const661         void operator()(Turn & turn) const
662         {
663             operation_type & op0 = turn.operations[0].operation;
664             operation_type & op1 = turn.operations[1].operation;
665 
666             // NOTE: probably only if methods are WRT IPs, not segments!
667             if ( BOOST_GEOMETRY_CONDITION(IsFront)
668               || op0 == operation_intersection || op0 == operation_union
669               || op1 == operation_intersection || op1 == operation_union )
670             {
671                 turn.method = m_method;
672             }
673 
674             turn.operations[0].is_collinear = op0 != operation_blocked;
675 
676             // Assuming G1 is always Linear
677             if ( op0 == operation_blocked )
678             {
679                 op0 = operation_continue;
680             }
681 
682             if ( op1 == operation_blocked )
683             {
684                 op1 = operation_continue;
685             }
686             else if ( op1 == operation_intersection )
687             {
688                 op1 = operation_union;
689             }
690         }
691 
692     private:
693         method_type m_method;
694     };
695 
replace_operations_iboost::geometry::detail::overlay::get_turn_info_linear_areal696     static inline void replace_operations_i(operation_type & /*op0*/, operation_type & op1)
697     {
698         // assuming Linear is always the first one
699         op1 = operation_union;
700     }
701 
702     // NOTE: Spikes may NOT be handled for Linear endpoints because it's not
703     //       possible to define a spike on an endpoint. Areal geometries must
704     //       NOT have spikes at all. One thing that could be done is to throw
705     //       an exception when spike is detected in Areal geometry.
706 
707     template <bool EnableFirst,
708               bool EnableLast,
709               typename Point1,
710               typename Point2,
711               typename TurnInfo,
712               typename IntersectionInfo,
713               typename OutputIterator>
get_turn_info_for_endpointboost::geometry::detail::overlay::get_turn_info_linear_areal714     static inline bool get_turn_info_for_endpoint(
715                             Point1 const& pi, Point1 const& /*pj*/, Point1 const& /*pk*/,
716                             Point2 const& qi, Point2 const& /*qj*/, Point2 const& /*qk*/,
717                             bool is_p_first, bool is_p_last,
718                             bool /*is_q_first*/, bool is_q_last,
719                             TurnInfo const& tp_model,
720                             IntersectionInfo const& inters,
721                             method_type /*method*/,
722                             OutputIterator out)
723     {
724         namespace ov = overlay;
725         typedef ov::get_turn_info_for_endpoint<AssignPolicy, EnableFirst, EnableLast> get_info_e;
726 
727         const std::size_t ip_count = inters.i_info().count;
728         // no intersection points
729         if ( ip_count == 0 )
730             return false;
731 
732         if ( !is_p_first && !is_p_last )
733             return false;
734 
735 // TODO: is_q_last could probably be replaced by false and removed from parameters
736 
737         linear_intersections intersections(pi, qi, inters.result(), is_p_last, is_q_last);
738         linear_intersections::ip_info const& ip0 = intersections.template get<0>();
739         linear_intersections::ip_info const& ip1 = intersections.template get<1>();
740 
741         const bool opposite = inters.d_info().opposite;
742 
743         // ANALYSE AND ASSIGN FIRST
744 
745         // IP on the first point of Linear Geometry
746         bool was_first_point_handled = false;
747         if ( BOOST_GEOMETRY_CONDITION(EnableFirst)
748           && is_p_first && ip0.is_pi && !ip0.is_qi ) // !q0i prevents duplication
749         {
750             TurnInfo tp = tp_model;
751             tp.operations[0].position = position_front;
752             tp.operations[1].position = position_middle;
753 
754             if ( opposite ) // opposite -> collinear
755             {
756                 tp.operations[0].operation = operation_continue;
757                 tp.operations[1].operation = operation_union;
758                 tp.method = ip0.is_qj ? method_touch : method_touch_interior;
759             }
760             else
761             {
762                 typedef typename IntersectionInfo::robust_point1_type rp1_type;
763                 typedef typename IntersectionInfo::robust_point2_type rp2_type;
764 
765                 method_type replaced_method = method_touch_interior;
766 
767                 if ( ip0.is_qj )
768                 {
769                     side_calculator
770                         <
771                             typename IntersectionInfo::cs_tag,
772                             rp1_type, rp2_type,
773                             typename IntersectionInfo::side_strategy_type,
774                             rp2_type
775                         > side_calc(inters.rqi(), inters.rpi(), inters.rpj(),
776                                     inters.rqi(), inters.rqj(), inters.rqk(),
777                                     inters.get_side_strategy());
778 
779                     std::pair<operation_type, operation_type>
780                         operations = get_info_e::operations_of_equal(side_calc);
781 
782                     tp.operations[0].operation = operations.first;
783                     tp.operations[1].operation = operations.second;
784 
785                     replaced_method = method_touch;
786                 }
787                 else
788                 {
789                     side_calculator
790                         <
791                             typename IntersectionInfo::cs_tag,
792                             rp1_type, rp2_type,
793                             typename IntersectionInfo::side_strategy_type,
794                             rp2_type, rp1_type, rp1_type,
795                             rp2_type, rp1_type, rp2_type
796                         > side_calc(inters.rqi(), inters.rpi(), inters.rpj(),
797                                     inters.rqi(), inters.rpi(), inters.rqj(),
798                                     inters.get_side_strategy());
799 
800                     std::pair<operation_type, operation_type>
801                         operations = get_info_e::operations_of_equal(side_calc);
802 
803                     tp.operations[0].operation = operations.first;
804                     tp.operations[1].operation = operations.second;
805                 }
806 
807                 turn_transformer_ec<true> transformer(replaced_method);
808                 transformer(tp);
809             }
810 
811             // equals<> or collinear<> will assign the second point,
812             // we'd like to assign the first one
813             base_turn_handler::assign_point(tp, tp.method, inters.i_info(), 0);
814 
815             // NOTE: is_collinear is not set for the first endpoint of L
816             // for which there is no preceding segment
817             // here is_p_first_ip == true
818             tp.operations[0].is_collinear = false;
819 
820             AssignPolicy::apply(tp, pi, qi, inters);
821             *out++ = tp;
822 
823             was_first_point_handled = true;
824         }
825 
826         // ANALYSE AND ASSIGN LAST
827 
828         // IP on the last point of Linear Geometry
829         if ( BOOST_GEOMETRY_CONDITION(EnableLast)
830           && is_p_last
831           && ( ip_count > 1 ? (ip1.is_pj && !ip1.is_qi) : (ip0.is_pj && !ip0.is_qi) ) ) // prevents duplication
832         {
833             TurnInfo tp = tp_model;
834 
835             if ( inters.i_info().count > 1 )
836             {
837                 //BOOST_GEOMETRY_ASSERT( result.template get<1>().dir_a == 0 && result.template get<1>().dir_b == 0 );
838                 tp.operations[0].is_collinear = true;
839                 tp.operations[1].operation = opposite ? operation_continue : operation_union;
840             }
841             else //if ( result.template get<0>().count == 1 )
842             {
843                 side_calculator
844                     <
845                         typename IntersectionInfo::cs_tag,
846                         typename IntersectionInfo::robust_point1_type,
847                         typename IntersectionInfo::robust_point2_type,
848                         typename IntersectionInfo::side_strategy_type,
849                         typename IntersectionInfo::robust_point2_type
850                     > side_calc(inters.rqi(), inters.rpj(), inters.rpi(),
851                                 inters.rqi(), inters.rqj(), inters.rqk(),
852                                 inters.get_side_strategy());
853 
854                 std::pair<operation_type, operation_type>
855                     operations = get_info_e::operations_of_equal(side_calc);
856 
857                 tp.operations[0].operation = operations.first;
858                 tp.operations[1].operation = operations.second;
859 
860                 turn_transformer_ec<false> transformer(method_none);
861                 transformer(tp);
862 
863                 tp.operations[0].is_collinear = tp.both(operation_continue);
864             }
865 
866             tp.method = ( ip_count > 1 ? ip1.is_qj : ip0.is_qj ) ? method_touch : method_touch_interior;
867             tp.operations[0].operation = operation_blocked;
868             tp.operations[0].position = position_back;
869             tp.operations[1].position = position_middle;
870 
871             // equals<> or collinear<> will assign the second point,
872             // we'd like to assign the first one
873             unsigned int ip_index = ip_count > 1 ? 1 : 0;
874             base_turn_handler::assign_point(tp, tp.method, inters.i_info(), ip_index);
875 
876             AssignPolicy::apply(tp, pi, qi, inters);
877             *out++ = tp;
878 
879             // don't ignore the first IP if the segment is opposite
880             return !( opposite && ip_count > 1 ) || was_first_point_handled;
881         }
882 
883         // don't ignore anything for now
884         return false;
885     }
886 };
887 
888 }} // namespace detail::overlay
889 #endif // DOXYGEN_NO_DETAIL
890 
891 }} // namespace boost::geometry
892 
893 #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GET_TURN_INFO_LA_HPP
894