Coverage for geometry/intersection.py: 62%

1"""

2 Intersection Utilities

3======================

4

5 Define utilities for computing geometric intersections and line segment

6 operations in two-dimensional space.

7

8 References

9----------

10- :cite:`Bourkea` : Bourke, P. (n.d.). Intersection point of two line

11 segments in 2 dimensions. Retrieved January 15, 2016, from

12 http://paulbourke.net/geometry/pointlineplane/

13- :cite:`Erdema` : Erdem, U. M. (n.d.). Fast Line Segment Intersection.

14 Retrieved January 15, 2016, from

15 http://www.mathworks.com/matlabcentral/fileexchange/\

16 27205-fast-line-segment-intersection

17- :cite:`Saeedna` : Saeedn. (n.d.). Extend a line segment a specific

18 distance. Retrieved January 16, 2016, from

19 http://stackoverflow.com/questions/7740507/\

20 extend-a-line-segment-a-specific-distance

21"""

22

23 from __future__ import annotations

24

25 import typing

26 from dataclasses import dataclass

27

28 import numpy as np

29

30 from colour.algebra import euclidean_distance, sdiv, sdiv_mode

31

32 if typing.TYPE_CHECKING:

33 from colour.hints import ArrayLike, NDArrayFloat

34

35 from colour.utilities import as_float_array, tsplit, tstack

36

37 __author__ = "Colour Developers"

38 __copyright__ = "Copyright 2013 Colour Developers"

39 __license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"

40 __maintainer__ = "Colour Developers"

41 __email__ = "colour-developers@colour-science.org"

42 __status__ = "Production"

43

44 __all__ = [

45 "extend_line_segment",

46 "LineSegmentsIntersections_Specification",

47 "intersect_line_segments",

48]

49

50

51 def extend_line_segment(

52 a: ArrayLike, b: ArrayLike, distance: float = 1

53) -> NDArrayFloat:

54 """

55 Extend the line segment defined by point arrays :math:`a` and :math:`b` by

56 the specified distance and generate the new end point.

57

58 Parameters

59 ----------

60 a

61 Point array :math:`a`.

62 b

63 Point array :math:`b`.

64 distance

65 Distance to extend the line segment.

66

67 Returns

68 -------

69 :class:`numpy.ndarray`

70 New end point.

71

72 References

73 ----------

74 :cite:`Saeedna`

75

76 Notes

77 -----

78 - Input line segment points coordinates are 2d coordinates.

79

80 Examples

81 --------

82 >>> a = np.array([0.95694934, 0.13720932])

83 >>> b = np.array([0.28382835, 0.60608318])

84 >>> extend_line_segment(a, b) # doctest: +ELLIPSIS

85 array([-0.5367248..., 1.1776534...])

86 """

87

88 x_a, y_a = tsplit(a)

89 x_b, y_b = tsplit(b)

90

91 d = euclidean_distance(a, b)

92

93 with sdiv_mode():

94 x_c = x_b + sdiv(x_b - x_a, d) * distance

95 y_c = y_b + sdiv(y_b - y_a, d) * distance

96

97 return tstack([x_c, y_c])

98

99

100@dataclass

101 class LineSegmentsIntersections_Specification:

102 """

103 Define the specification for intersection of line segments :math:`l_1` and

104 :math:`l_2` returned by the

105 :func:`colour.algebra.intersect_line_segments` definition.

106

107 Parameters

108 ----------

109 xy

110 Array of :math:`l_1` and :math:`l_2` line segments intersections

111 coordinates. Non-existing segments intersections coordinates are set

112 with `np.nan`.

113 intersect

114 Array of *bool* indicating if line segments :math:`l_1` and

115 :math:`l_2` intersect.

116 parallel

117 Array of :class:`bool` indicating if line segments :math:`l_1` and

118 :math:`l_2` are parallel.

119 coincident

120 Array of :class:`bool` indicating if line segments :math:`l_1` and

121 :math:`l_2` are coincident.

122 """

123

124 xy: NDArrayFloat

125 intersect: NDArrayFloat

126 parallel: NDArrayFloat

127 coincident: NDArrayFloat

128

129

130 def intersect_line_segments(

131 l_1: ArrayLike, l_2: ArrayLike

132) -> LineSegmentsIntersections_Specification:

133 """

134 Compute :math:`l_1` line segments intersections with :math:`l_2` line

135 segments.

136

137 Parameters

138 ----------

139 l_1

140 :math:`l_1` line segments array, each row is a line segment such as

141 (:math:`x_1`, :math:`y_1`, :math:`x_2`, :math:`y_2`) where

142 (:math:`x_1`, :math:`y_1`) and (:math:`x_2`, :math:`y_2`) are

143 respectively the start and end points of :math:`l_1` line segments.

144 l_2

145 :math:`l_2` line segments array, each row is a line segment such as

146 (:math:`x_3`, :math:`y_3`, :math:`x_4`, :math:`y_4`) where

147 (:math:`x_3`, :math:`y_3`) and (:math:`x_4`, :math:`y_4`) are

148 respectively the start and end points of :math:`l_2` line segments.

149

150 Returns

151 -------

152 :class:`colour.algebra.LineSegmentsIntersections_Specification`

153 Line segments intersections specification.

154

155 References

156 ----------

157 :cite:`Bourkea`, :cite:`Erdema`

158

159 Notes

160 -----

161 - Input line segments points coordinates are 2d coordinates.

162

163 Examples

164 --------

165 >>> l_1 = np.array(

166 ... [

167 ... [[0.15416284, 0.7400497], [0.26331502, 0.53373939]],

168 ... [[0.01457496, 0.91874701], [0.90071485, 0.03342143]],

169 ... ]

170 ... )

171 >>> l_2 = np.array(

172 ... [

173 ... [[0.95694934, 0.13720932], [0.28382835, 0.60608318]],

174 ... [[0.94422514, 0.85273554], [0.00225923, 0.52122603]],

175 ... [[0.55203763, 0.48537741], [0.76813415, 0.16071675]],

176 ... ]

177 ... )

178 >>> s = intersect_line_segments(l_1, l_2)

179 >>> s.xy # doctest: +ELLIPSIS

180 array([[[ nan, nan],

181 [ 0.2279184..., 0.6006430...],

182 [ nan, nan]],

183 <BLANKLINE>

184 [[ 0.4281451..., 0.5055568...],

185 [ 0.3056055..., 0.6279838...],

186 [ 0.7578749..., 0.1761301...]]])

187 >>> s.intersect

188 array([[False, True, False],

189 [ True, True, True]], dtype=bool)

190 >>> s.parallel

191 array([[False, False, False],

192 [False, False, False]], dtype=bool)

193 >>> s.coincident

194 array([[False, False, False],

195 [False, False, False]], dtype=bool)

196 """

197

198 l_1 = as_float_array(l_1)

199 l_2 = as_float_array(l_2)

200

201 l_1 = np.reshape(l_1, (-1, 4))

202 l_2 = np.reshape(l_2, (-1, 4))

203

204 r_1, c_1 = l_1.shape[0], l_1.shape[1]

205 r_2, c_2 = l_2.shape[0], l_2.shape[1]

206

207 x_1, y_1, x_2, y_2 = (np.tile(l_1[:, i, None], (1, r_2)) for i in range(c_1))

208

209 l_2 = np.transpose(l_2)

210

211 x_3, y_3, x_4, y_4 = (np.tile(l_2[i, :], (r_1, 1)) for i in range(c_2))

212

213 x_4_x_3 = x_4 - x_3

214 y_1_y_3 = y_1 - y_3

215 y_4_y_3 = y_4 - y_3

216 x_1_x_3 = x_1 - x_3

217 x_2_x_1 = x_2 - x_1

218 y_2_y_1 = y_2 - y_1

219

220 numerator_a = x_4_x_3 * y_1_y_3 - y_4_y_3 * x_1_x_3

221 numerator_b = x_2_x_1 * y_1_y_3 - y_2_y_1 * x_1_x_3

222 denominator = y_4_y_3 * x_2_x_1 - x_4_x_3 * y_2_y_1

223

224 with sdiv_mode("Ignore"):

225 u_a = sdiv(numerator_a, denominator)

226 u_b = sdiv(numerator_b, denominator)

227

228 intersect = np.logical_and.reduce((u_a >= 0, u_a <= 1, u_b >= 0, u_b <= 1))

229 xy = tstack([x_1 + x_2_x_1 * u_a, y_1 + y_2_y_1 * u_a])

230 xy[~intersect] = np.nan

231 parallel = denominator == 0

232 coincident = np.logical_and.reduce((numerator_a == 0, numerator_b == 0, parallel))

233

234 return LineSegmentsIntersections_Specification(xy, intersect, parallel, coincident)