Coverage for pygeodesy/resections.py: 97%

1

2# -*- coding: utf-8 -*-

3

4 u'''3-Point resection functions L{cassini}, L{collins5}, L{pierlot}, L{pierlotx} and

5 L{tienstra7}, survey functions L{snellius3} and L{wildberger3} and triangle functions

6 L{triAngle}, L{triAngle5}, L{triSide}, L{triSide2} and L{triSide4}.

7

8@note: Functions L{pierlot} and L{pierlotx} are transcoded to Python with permission from

9 U{triangulationPierlot<http://www.Telecom.ULg.ac.BE/triangulation/doc/total_8c.html>} and

10 U{Pierlot<http://www.Telecom.ULg.ac.BE/publi/publications/pierlot/Pierlot2014ANewThree>}.

11'''

12# make sure int/int division yields float quotient

13 from __future__ import division as _; del _ # noqa: E702 ;

14

15 from pygeodesy.basics import map1, map2, _zip, _ALL_LAZY, typename

16 from pygeodesy.constants import EPS, EPS0, EPS02, INT0, PI, PI2, PI_2, PI_4, \

17 _0_0, _0_5, _1_0, _N_1_0, _2_0, _N_2_0, _4_0, \

18 _16_0, _180_0, _360_0, isnear0, _over, _umod_360

19 from pygeodesy.errors import _and, _or, TriangleError, _ValueError, _xcallable, \

20 _xkwds, _xkwds_pop2

21 from pygeodesy.fmath import favg, Fdot, Fdot_, fidw, _fma, fmean, hypot, hypot2_

22 from pygeodesy.fsums import _Fsumf_, fsumf_, fsum1, fsum1f_

23# from pygeodesy.internals import typename # from .basics

24 from pygeodesy.interns import _a_, _A_, _area_, _b_, _B_, _c_, _C_, _coincident_, \

25 _colinear_, _d_, _invalid_, _negative_, _rIn_, _SPACE_

26# from pygeodesy.lazily import _ALL_LAZY # from .basics

27 from pygeodesy.named import _NamedTuple, _Pass, Fmt

28# from pygeodesy.streprs import Fmt # from .named

29 from pygeodesy.units import Degrees, Distance, Radians

30 from pygeodesy.utily import acos1, asin1, atan2, sincos2, sincos2_, \

31 sincos2d, sincos2d_

32 from pygeodesy.vector3d import _otherV3d, Vector3d

33

34 from math import cos, degrees, fabs, radians, sin, sqrt

35

36 __all__ = _ALL_LAZY.resections

37 __version__ = '25.05.12'

38

39 _concyclic_ = 'concyclic'

40 _PA_ = 'PA'

41 _PB_ = 'PB'

42 _PC_ = 'PC'

43 _pointH_ = 'pointH'

44 _pointP_ = 'pointP'

45 _radA_ = 'radA'

46 _radB_ = 'radB'

47 _radC_ = 'radC'

48

49

50 class Collins5Tuple(_NamedTuple):

51 '''5-Tuple C{(pointP, pointH, a, b, c)} with survey C{pointP}, auxiliary

52 C{pointH}, each an instance of B{C{pointA}}'s (sub-)class and triangle

53 sides C{a}, C{b} and C{c} in C{meter}, conventionally.

54 '''

55 _Names_ = (_pointP_, _pointH_, _a_, _b_, _c_)

56 _Units_ = (_Pass, _Pass, Distance, Distance, Distance)

57

58

59 class ResectionError(_ValueError):

60 '''Error raised for issues in L{pygeodesy.resections}.

61 '''

62 pass

63

64

65 class Survey3Tuple(_NamedTuple):

66 '''3-Tuple C{(PA, PB, PC)} with distance from survey point C{P} to each of

67 the triangle corners C{A}, C{B} and C{C} in C{meter}, conventionally.

68 '''

69 _Names_ = (_PA_, _PB_, _PC_)

70 _Units_ = ( Distance, Distance, Distance)

71

72

73 class Tienstra7Tuple(_NamedTuple):

74 '''7-Tuple C{(pointP, A, B, C, a, b, c)} with survey C{pointP}, interior

75 triangle angles C{A}, C{B} and C{C} in C{degrees} and triangle sides

76 C{a}, C{b} and C{c} in C{meter}, conventionally.

77 '''

78 _Names_ = (_pointP_, _A_, _B_, _C_, _a_, _b_, _c_)

79 _Units_ = (_Pass, Degrees, Degrees, Degrees, Distance, Distance, Distance)

80

81

82 class TriAngle5Tuple(_NamedTuple):

83 '''5-Tuple C{(radA, radB, radC, rIn, area)} with the interior angles at

84 triangle corners C{A}, C{B} and C{C} in C{radians}, the C{InCircle}

85 radius C{rIn} aka C{inradius} in C{meter} and the triangle C{area}

86 in C{meter} I{squared}, conventionally.

87 '''

88 _Names_ = (_radA_, _radB_, _radC_, _rIn_, _area_)

89 _Units_ = ( Radians, Radians, Radians, Distance, _Pass)

90

91

92 class TriSide2Tuple(_NamedTuple):

93 '''2-Tuple C{(a, radA)} with triangle side C{a} in C{meter}, conventionally

94 and angle C{radA} at the opposite triangle corner in C{radians}.

95 '''

96 _Names_ = (_a_, _radA_)

97 _Units_ = ( Distance, Radians)

98

99

100 class TriSide4Tuple(_NamedTuple):

101 '''4-Tuple C{(a, b, radC, d)} with interior angle C{radC} at triangle corner

102 C{C} in C{radians}with the length of triangle sides C{a} and C{b} and

103 with triangle height C{d} perpendicular to triangle side C{c}, in the

104 same units as triangle sides C{a} and C{b}.

105 '''

106 _Names_ = (_a_, _b_, _radC_, _d_)

107 _Units_ = ( Distance, Distance, Radians, Distance)

108

109

110 def _ABC3(useZ, pointA, pointB, pointC):

111 '''(INTERNAL) Helper for L{cassini} and L{tienstra7}.

112 '''

113 return (_otherV3d(useZ=useZ, pointA=pointA),

114 _otherV3d(useZ=useZ, pointB=pointB),

115 _otherV3d(useZ=useZ, pointC=pointC))

116

117

118 def _B3(useZ, point1, point2, point3):

119 '''(INTERNAL) Helper for L{pierlot} and L{pierlotx}.

120 '''

121 return (_otherV3d(useZ=useZ, point1=point1),

122 _otherV3d(useZ=useZ, point2=point2),

123 _otherV3d(useZ=useZ, point3=point3))

124

125

126 def cassini(pointA, pointB, pointC, alpha, beta, useZ=False, **Clas_and_kwds):

127 '''3-Point resection using U{Cassini<https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.

128

129 @arg pointA: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

130 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

131 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

132 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

133 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

134 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

135 @arg alpha: Angle subtended by triangle side B{C{pointA}} to B{C{pointC}}

136 (C{degrees}, non-negative).

137 @arg beta: Angle subtended by triangle side B{C{pointB}} to B{C{pointC}}

138 (C{degrees}, non-negative).

139 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

140 force C{z=INT0} (C{bool}).

141 @kwarg Clas_and_kwds: Optional class C{B{Clas}=B{pointA}.classof} to

142 return the survey point with optionally other B{C{Clas}}

143 keyword arguments to instantiate the survey point.

144

145 @note: Typically, B{C{pointC}} is between B{C{pointA}} and B{C{pointB}}.

146

147 @return: The survey point, an instance of B{C{Clas}} or B{C{pointA}}'s

148 (sub-)class.

149

150 @raise ResectionError: Near-coincident, -colinear or -concyclic points

151 or negative or invalid B{C{alpha}} or B{C{beta}}.

152

153 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.

154

155 @see: U{Three Point Resection Problem<https://Dokumen.tips/documents/

156 three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}

157 and functions L{collins5}, L{pierlot}, L{pierlotx} and L{tienstra7}.

158 '''

159

160 def _H(A, C, sa):

161 s, c = sincos2d(sa)

162 if isnear0(s):

163 raise ValueError(_or(_coincident_, _colinear_))

164 t = s, c, c

165 x = Fdot(t, A.x, C.y, -A.y).fover(s)

166 y = Fdot(t, A.y, -C.x, A.x).fover(s)

167 return x, y

168

169 A, B, C = _ABC3(useZ, pointA, pointB, pointC)

170 try:

171 sa, sb = _noneg(alpha, beta)

172 if fsumf_(_360_0, -sa, -sb) < EPS0:

173 raise ValueError(_colinear_)

174

175 x1, y1 = _H(A, C, sa)

176 x2, y2 = _H(B, C, -sb)

177

178 x = x1 - x2

179 y = y1 - y2

180 if isnear0(x) or isnear0(y):

181 raise ValueError(_SPACE_(_concyclic_, (x, y)))

182

183 m = y / x

184 n = x / y

185 N = n + m

186 if isnear0(N):

187 raise ValueError(_SPACE_(_concyclic_, (m, n, N)))

188

189 t = n, m, _1_0, _N_1_0

190 x = Fdot(t, C.x, x1, C.y, y1).fover(N)

191 y = Fdot(t, y1, C.y, C.x, x1).fover(N)

192 z = _zidw(x, y, useZ, A, B, C)

193 return _Clas(cassini, pointA, Clas_and_kwds, x, y, z)

194

195 except (TypeError, ValueError) as x:

196 raise ResectionError(pointA=pointA, pointB=pointB, pointC=pointC,

197 alpha=alpha, beta=beta, cause=x)

198

199

200 def _Clas(which, point, Clas_and_kwds, *args):

201 '''(INTERNAL) Return a C{B{Clas}=point.classof} survey point.

202 '''

203 Clas, kwds = _xkwds_pop2(Clas_and_kwds, Clas=point.classof)

204 return Clas(*args, **_xkwds(kwds, name=typename(which)))

205

206

207 def collins5(pointA, pointB, pointC, alpha, beta, useZ=False, **Clas_and_kwds):

208 '''3-Point resection using U{Collins<https://Dokumen.tips/documents/

209 three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.

210

211 @arg pointA: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

212 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

213 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

214 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

215 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

216 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

217 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to

218 B{C{pointC}} (C{degrees}, non-negative).

219 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to

220 B{C{pointC}} (C{degrees}, non-negative).

221 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

222 force C{z=INT0} (C{bool}).

223 @kwarg Clas_and_kwds: Optional class C{B{Clas}=B{pointA}.classof} to

224 return the survey point with optionally other B{C{Clas}}

225 keyword arguments to instantiate the survey point.

226

227 @note: Typically, B{C{pointC}} is between B{C{pointA}} and B{C{pointB}}.

228

229 @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},

230 auxiliary C{pointH}, each an instance of B{C{Clas}} or B{C{pointA}}'s

231 (sub-)class and triangle sides C{a}, C{b} and C{c} in C{meter},

232 conventionally.

233

234 @raise ResectionError: Near-coincident, -colinear or -concyclic points

235 or negative or invalid B{C{alpha}} or B{C{beta}}.

236

237 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.

238

239 @see: U{Collins' methode<https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}

240 and functions L{cassini}, L{pierlot}, L{pierlotx} and L{tienstra7}.

241 '''

242

243 def _azi_len2(A, B, pi2=PI2):

244 v = B.minus(A)

245 r = atan2(v.x, v.y)

246 if r < 0 and pi2:

247 r += pi2

248 return r, v.length

249

250 def _xyz(d, r, A, B, C, useZ):

251 s, c = sincos2(r)

252 x = _fma(d, s, A.x)

253 y = _fma(d, c, A.y)

254 z = _zidw(x, y, useZ, A, B, C)

255 return x, y, z

256

257 A, B, C = _ABC3(useZ, pointA, pointB, pointC)

258 try:

259 ra, rb = t = radians(alpha), radians(beta)

260 if min(t) < 0:

261 raise ValueError(_negative_)

262

263 sra, srH = sin(ra), sin(ra + rb - PI) # rH = PI - ((PI - ra) + (PI - rb))

264 if isnear0(sra) or isnear0(srH):

265 raise ValueError(_or(_coincident_, _colinear_, _concyclic_))

266

267# za, a = _azi_len2(C, B)

268 zb, b = _azi_len2(C, A)

269 zc, c = _azi_len2(A, B, 0)

270

271# d = c * sin(PI - rb) / srH # B.minus(H).length

272 d = c * sin(PI - ra) / srH # A.minus(H).length

273 r = zc + PI - rb # zh = zc + (PI - rb)

274 H = _xyz(d, r, A, B, C, useZ)

275

276 zh, _ = _azi_len2(C, Vector3d(*H))

277

278# d = a * sin(za - zh) / sin(rb) # B.minus(P).length

279 d = b * sin(zb - zh) / sra # A.minus(P).length

280 r = zh - ra # zb - PI + (PI - ra - (zb - zh))

281 P = _xyz(d, r, A, B, C, useZ)

282

283 P = _Clas(collins5, pointA, Clas_and_kwds, *P)

284 H = _Clas(collins5, pointA, Clas_and_kwds, *H)

285 a = B.minus(C).length

286 return Collins5Tuple(P, H, a, b, c, name=typename(collins5))

287

288 except (TypeError, ValueError) as x:

289 raise ResectionError(pointA=pointA, pointB=pointB, pointC=pointC,

290 alpha=alpha, beta=beta, cause=x)

291

292

293 def _noneg(*xs):

294 '''(INTERNAL) Return non-negative C{float}s.

295 '''

296 xs = tuple(map(float, xs))

297 if min(xs) < 0:

298 raise ValueError(_negative_)

299 return xs

300

301

302 def pierlot(point1, point2, point3, alpha12, alpha23, useZ=False, eps=EPS,

303 **Clas_and_kwds):

304 '''3-Point resection using U{Pierlot<http://www.Telecom.ULg.ac.BE/publi/publications/

305 pierlot/Pierlot2014ANewThree>}'s method C{ToTal} with I{approximate} limits for

306 the (pseudo-)singularities.

307

308 @arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

309 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

310 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

311 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

312 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

313 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

314 @arg alpha12: Angle subtended from B{C{point1}} to B{C{point2}} or

315 B{C{alpha2 - alpha1}} (C{degrees}).

316 @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or

317 B{C{alpha3 - alpha2}}(C{degrees}).

318 @kwarg useZ: If C{True}, interpolate the survey point's Z component,

319 otherwise use C{z=INT0} (C{bool}).

320 @kwarg eps: Tolerance for C{cot}angent (pseudo-)singularities (C{float}).

321 @kwarg Clas_and_kwds: Optional class C{B{Clas}=B{point1}.classof} to

322 return the survey point with optionally other B{C{Clas}}

323 keyword arguments to instantiate the survey point.

324

325 @note: Typically, B{C{point1}}, B{C{point2}} and B{C{point3}} are ordered

326 by angle, modulo 360, counter-clockwise.

327

328 @return: The survey (or robot) point, an instance of B{C{Clas}} or B{C{point1}}'s

329 (sub-)class.

330

331 @raise ResectionError: Near-coincident, -colinear or -concyclic points

332 or invalid B{C{alpha12}} or B{C{alpha23}} or

333 non-positive B{C{eps}}.

334

335 @raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.

336

337 @see: I{Pierlot}'s C function U{triangulationPierlot<http://www.Telecom.ULg.ac.BE/

338 triangulation/doc/total_8c_source.html>}, U{V. Pierlot, M. Van Droogenbroeck,

339 "A New Three Object Triangulation Algorithm for Mobile Robot Positioning"

340 <https://ORBi.ULiege.BE/bitstream/2268/157469/1/Pierlot2014ANewThree.pdf>},

341 U{Vincent Pierlot, Marc Van Droogenbroeck, "18 Triangulation Algorithms for 2D

342 Positioning (also known as the Resection Problem)"<http://www.Telecom.ULg.ac.BE/

343 triangulation>} and functions L{pierlotx}, L{cassini}, L{collins5} and L{tienstra7}.

344 '''

345

346 def _cot(s, c): # -eps < I{approximate} cotangent < eps

347 if eps > 0:

348 return c / (min(s, -eps) if s < 0 else max(s, eps))

349 t = Fmt.PARENSPACED(eps=eps)

350 raise ValueError(_SPACE_(t, _invalid_))

351

352 B1, B2, B3 = _B3(useZ, point1, point2, point3)

353 try:

354 xyz = _pierlot3(B1, B2, B3, alpha12, alpha23, useZ, _cot)

355 return _Clas(pierlot, point1, Clas_and_kwds, *xyz)

356

357 except (TypeError, ValueError) as x:

358 raise ResectionError(point1=point1, point2=point2, point3=point3,

359 alpha12=alpha12, alpha23=alpha23, eps=eps, cause=x)

360

361

362 def _pierlot3(B1, B2, B3, a12, a23, useZ, _cot):

363 '''(INTERNAL) Shared L{pierlot} and L{pierlotx}.

364 '''

365 x1_, y1_, _ = B1.minus(B2).xyz3

366 x3_, y3_, _ = B3.minus(B2).xyz3

367

368 s12, c12, s23, c23 = sincos2d_(a12, a23)

369 # cot31 = (1 - cot12 * cot23) / (cot12 + cot32)

370 # = (1 - c12 / s12 * c23 / s23) / (c12 / s12 + c23 / s23)

371 # = (1 - (c12 * c23) / (s12 * s23)) / (c12 * s23 + s12 * c23) / (s12 * s23)

372 # = (s12 * s23 - c12 * c23) / (c12 * s23 + s12 * c23)

373 # = c31 / s31

374 cot31 = _cot(fsum1f_(c12 * s23, s12 * c23), # s31

375 fsum1f_(s12 * s23, -c12 * c23)) # c31

376

377 K = _Fsumf_(x3_ * x1_, cot31 * (y3_ * x1_),

378 y3_ * y1_, -cot31 * (x3_ * y1_))

379 if K:

380 cot12 = _cot(s12, c12)

381 cot23 = _cot(s23, c23)

382

383 # x12 = x1_ + cot12 * y1_

384 # y12 = y1_ - cot12 * x1_

385

386 # x23 = x3_ - cot23 * y3_

387 # y23 = y3_ + cot23 * x3_

388

389 # x31 = x3_ + x1_ + cot31 * (y3_ - y1_)

390 # y31 = y3_ + y1_ - cot31 * (x3_ - x1_)

391

392 # x12 - x23 = x1_ + cot12 * y1_ - x3_ + cot23 * y3_

393 X12_23 = _Fsumf_(x1_, cot12 * y1_, -x3_, cot23 * y3_)

394 # y12 - y23 = y1_ - cot12 * x1_ - y3_ - cot23 * x3_

395 Y12_23 = _Fsumf_(y1_, -cot12 * x1_, -y3_, -cot23 * x3_)

396

397 # x31 - x23 = x3_ + x1_ + cot31 * (y3_ - y1_) - x3_ + cot23 * y3_

398 # = x1_ + cot31 * y3_ - cot31 * y1_ + cot23 * y3_

399 X31_23 = _Fsumf_(x1_, -cot31 * y1_, cot31 * y3_, cot23 * y3_)

400 # y31 - y23 = y3_ + y1_ - cot31 * (x3_ - x1_) - y3_ - cot23 * x3_

401 # = y1_ - cot31 * x3_ + cot31 * x1_ - cot23 * x3_

402 Y31_23 = _Fsumf_(y1_, cot31 * x1_, -cot31 * x3_, -cot23 * x3_)

403

404 # d = (x12 - x23) * (y23 - y31) + (x31 - x23) * (y12 - y23)

405 # = (x31 - x23) * (y12 - y23) - (x12 - x23) * (y31 - y23)

406 # x = (d * B2.x + K * Y12_23).fover(d)

407 # y = (d * B2.y - K * X12_23).fover(d)

408 x, y = _pierlotxy2(B2, -K, Y12_23, X12_23, Fdot_(X31_23, Y12_23,

409 -X12_23, Y31_23))

410 else:

411 x, y, _ = B2.xyz3

412 return x, y, _zidw(x, y, useZ, B1, B2, B3)

413

414

415 def pierlotx(point1, point2, point3, alpha1, alpha2, alpha3, useZ=False,

416 **Clas_and_kwds):

417 '''3-Point resection using U{Pierlot<http://www.Telecom.ULg.ac.BE/publi/

418 publications/pierlot/Pierlot2014ANewThree>}'s method C{ToTal} with

419 I{exact} limits for the (pseudo-)singularities.

420

421 @arg point1: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

422 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

423 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

424 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

425 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

426 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

427 @arg alpha1: Angle at B{C{point1}} (C{degrees}, counter-clockwise).

428 @arg alpha2: Angle at B{C{point2}} (C{degrees}, counter-clockwise).

429 @arg alpha3: Angle at B{C{point3}} (C{degrees}, counter-clockwise).

430 @kwarg useZ: If C{True}, interpolate the survey point's Z component,

431 otherwise use C{z=INT0} (C{bool}).

432 @kwarg Clas_and_kwds: Optional class C{B{Clas}=B{point1}.classof} to

433 return the survey point with optionally other B{C{Clas}}

434 keyword arguments to instantiate the survey point.

435

436 @return: The survey (or robot) point, an instance of B{C{Clas}} or

437 B{C{point1}}'s (sub-)class.

438

439 @raise ResectionError: Near-coincident, -colinear or -concyclic points or

440 invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.

441

442 @raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.

443

444 @see: I{Pierlot}'s C function U{triangulationPierlot2<http://www.Telecom.ULg.ac.BE/

445 triangulation/doc/total_8c_source.html>} and function L{pierlot}, L{cassini},

446 L{collins5} and L{tienstra7}.

447 '''

448

449 def _a_z_Bs(Bs, *alphas):

450 ds = map2(_umod_360, alphas) # 0 <= alphas < 360

451 ds, Bs = zip(*sorted(_zip(ds, Bs))) # unzip

452 for p, d, B in _zip(ds, _rotate(ds), Bs):

453 d -= p # a12 = a2 - a1, ...

454 z = isnear0(fabs(d) % _180_0)

455 yield d, z, B

456

457 def _cot(s, c): # I{exact} cotangent

458 try:

459 return (c / s) # if c else _copysign_0_0(s)

460 except ZeroDivisionError:

461 raise ValueError(_or(_coincident_, _colinear_))

462

463 Bs = _B3(useZ, point1, point2, point3)

464 try:

465 Cs = [0] # pseudo-global, passing the exception Case

466 xyz = _pierlotx3(_a_z_Bs(Bs, alpha1, alpha2, alpha3),

467 useZ, _cot, Cs.append)

468 return _Clas(pierlotx, point1, Clas_and_kwds, *xyz)

469

470 except (TypeError, ValueError) as x:

471 raise ResectionError(point1=point1, point2=point2, point3=point3, C=Cs.pop(),

472 alpha1=alpha1, alpha2=alpha2, alpha3=alpha3, cause=x)

473

474

475 def _pierlotx3(a_z_Bs, useZ, _cot, Cs):

476 '''(INTERNAL) Core of L{pierlotx}.

477 '''

478 (a12, z12, B1), \

479 (a23, z23, B2), \

480 (a31, z31, B3) = a_z_Bs

481 if z12 and not z23:

482 Cs(1)

483 elif z23 and not z31:

484 Cs(2)

485 a23, B1, B2, B3 = a31, B2, B3, B1

486 elif z31 and not z12:

487 Cs(3)

488 a23, B2, B3 = a12, B3, B2

489 else:

490 Cs(4)

491 return _pierlot3(B1, B2, B3, a12, a23, useZ, _cot)

492

493 x1_, y1_, _ = B1.minus(B3).xyz3

494 x2_, y2_, _ = B2.minus(B3).xyz3

495

496 K = _Fsumf_(y1_ * x2_, -x1_ * y2_)

497 if K:

498 cot23 = _cot(*sincos2d(a23))

499

500 # x23 = x2_ + cot23 * y2_ # _fma( cot23, y2_, x2_)

501 # y23 = y2_ - cot23 * x2_ # _fma(-cot23, x2_, y2_)

502

503 # x31 = x1_ + cot23 * y1_ # _fma( cot23, y1_, x1_)

504 # y31 = y1_ - cot23 * x1_ # _fma(-cot23, x1_, y1_)

505

506 # x31 - x23 = x1_ + cot23 * y1_ - x2_ - cot23 * y2_

507 X31_23 = _Fsumf_(x1_, cot23 * y1_, -x2_, -cot23 * y2_)

508 # y31 - y23 = y1_ - cot23 * x1_ - y2_ + cot23 * x2_

509 Y31_23 = _Fsumf_(y1_, -cot23 * x1_, -y2_, cot23 * x2_)

510

511 # d = (x31 - x23) * (x2_ - x1_) + (y31 - y23) * (y2_ - y1_)

512 # x = (d * B3.x - K * Y31_23).fover(d)

513 # y = (d * B3.y + K * X31_23).fover(d)

514 x, y = _pierlotxy2(B3, K, Y31_23, X31_23, Fdot_(X31_23, _Fsumf_(x2_, -x1_),

515 Y31_23, _Fsumf_(y2_, -y1_)))

516 else:

517 x, y, _ = B3.xyz3

518 return x, y, _zidw(x, y, useZ, B1, B2, B3)

519

520

521 def _pierlotxy2(B, K, X, Y, D):

522 '''(INTERNAL) Helper for C{_pierlot3} and C{_pierlotx3}.

523 '''

524 d = float(D)

525 if isnear0(d):

526 raise ValueError(_or(_coincident_, _colinear_, _concyclic_))

527 x = Fdot_(D, B.x, -K, X).fover(d)

528 y = Fdot_(D, B.y, K, Y).fover(d)

529 return x, y

530

531

532 def _rotate(xs, n=1):

533 '''Rotate list or tuple C{xs} by C{n} items, right if C{n > 0} else left.

534 '''

535 return xs[n:] + xs[:n]

536

537

538 def snellius3(a, b, degC, alpha, beta):

539 '''Snellius' surveying using U{Snellius Pothenot<https://WikiPedia.org/wiki/Snellius–Pothenot_problem>}.

540

541 @arg a: Length of the triangle side between corners C{B} and C{C} and opposite of

542 triangle corner C{A} (C{scalar}, non-negative C{meter}, conventionally).

543 @arg b: Length of the triangle side between corners C{C} and C{A} and opposite of

544 triangle corner C{B} (C{scalar}, non-negative C{meter}, conventionally).

545 @arg degC: Angle at triangle corner C{C}, opposite triangle side C{c} (non-negative C{degrees}).

546 @arg alpha: Angle subtended by triangle side B{C{b}} (non-negative C{degrees}).

547 @arg beta: Angle subtended by triangle side B{C{a}} (non-negative C{degrees}).

548

549 @return: L{Survey3Tuple}C{(PA, PB, PC)} with distance from survey point C{P} to

550 each of the triangle corners C{A}, C{B} and C{C}, same units as triangle

551 sides B{C{a}}, B{C{b}} and B{C{c}}.

552

553 @raise TriangleError: Invalid B{C{a}}, B{C{b}} or B{C{degC}} or negative B{C{alpha}}

554 or B{C{beta}}.

555

556 @see: Function L{wildberger3}.

557 '''

558 try:

559 a, b, degC, alpha, beta = _noneg(a, b, degC, alpha, beta)

560 ra, rb, rC = map1(radians, alpha, beta, degC)

561

562 r = fsum1f_(ra, rb, rC) * _0_5

563 k = PI - r

564 if min(k, r) < 0:

565 raise ValueError(_or(_coincident_, _colinear_))

566

567 sa, sb = map1(sin, ra, rb)

568 p = atan2(sa * a, sb * b)

569 sp, cp, sr, cr = sincos2_(PI_4 - p, r)

570 p = atan2(sp * sr, cp * cr)

571 pa = k + p

572 pb = k - p

573

574 if fabs(sb) > fabs(sa):

575 pc = fabs(a * sin(pb) / sb)

576 elif sa:

577 pc = fabs(b * sin(pa) / sa)

578 else:

579 raise ValueError(_or(_colinear_, _coincident_))

580

581 pa = _triSide(b, pc, fsumf_(PI, -ra, -pa))

582 pb = _triSide(a, pc, fsumf_(PI, -rb, -pb))

583 return Survey3Tuple(pa, pb, pc, name=typename(snellius3))

584

585 except (TypeError, ValueError) as x:

586 raise TriangleError(a=a, b=b, degC=degC, alpha=alpha, beta=beta, cause=x)

587

588

589 def tienstra7(pointA, pointB, pointC, alpha, beta=None, gamma=None,

590 useZ=False, **Clas_and_kwds):

591 '''3-Point resection using U{Tienstra<https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.

592

593 @arg pointA: First point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

594 C{Vector2Tuple} if C{B{useZ}=False}).

595 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

596 C{Vector2Tuple} if C{B{useZ}=False}).

597 @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

598 C{Vector2Tuple} if C{B{useZ}=False}).

599 @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}}

600 (C{degrees}, non-negative).

601 @kwarg beta: Angle subtended by triangle side C{b} from B{C{pointA}} to B{C{pointC}}

602 (C{degrees}, non-negative) or C{None} if C{B{gamma} is not None}.

603 @kwarg gamma: Angle subtended by triangle side C{c} from B{C{pointA}} to B{C{pointB}}

604 (C{degrees}, non-negative) or C{None} if C{B{beta} is not None}.

605 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}

606 (C{bool}).

607 @kwarg Clas_and_kwds: Optional class C{B{Clas}=B{pointA}.classof} to return the survey

608 point with optionally other B{C{Clas}} keyword arguments to instantiate

609 the survey point.

610

611 @note: Points B{C{pointA}}, B{C{pointB}} and B{C{pointC}} are ordered clockwise.

612

613 @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP}, an

614 instance of B{C{Clas}} or B{C{pointA}}'s (sub-)class, with triangle angles C{A}

615 at B{C{pointA}}, C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees}

616 and with triangle sides C{a}, C{b} and C{c} in C{meter}, conventionally.

617

618 @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of

619 B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or negative

620 B{C{alpha}}, B{C{beta}} or B{C{gamma}}.

621

622 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointC}}.

623

624 @see: U{3-Point Resection Solver<http://MesaMike.org/geocache/GC1B0Q9/tienstra/>},

625 U{V. Pierlot, M. Van Droogenbroeck, "A New Three Object Triangulation..."

626 <http://www.Telecom.ULg.ac.BE/publi/publications/pierlot/Pierlot2014ANewThree/>},

627 U{18 Triangulation Algorithms...<http://www.Telecom.ULg.ac.BE/triangulation/>} and

628 functions L{cassini}, L{collins5}, L{pierlot} and L{pierlotx}.

629 '''

630

631 def _deg_ks(r, s, ks, N):

632 if isnear0(fsumf_(PI, r, -s)): # r + (PI2 - s) == PI

633 raise ValueError(Fmt.PARENSPACED(concyclic=N))

634 # k = 1 / (cot(r) - cot(s))

635 # = 1 / (cos(r) / sin(r) - cos(s) / sin(s))

636 # = 1 / (cos(r) * sin(s) - cos(s) * sin(r)) / (sin(r) * sin(s))

637 # = sin(r) * sin(s) / (cos(r) * sin(s) - cos(s) * sin(r))

638 sr, cr, ss, cs = sincos2_(r, s)

639 c = fsum1f_(cr * ss, -cs * sr)

640 if isnear0(c):

641 raise ValueError(Fmt.PARENSPACED(cotan=N))

642 ks.append(sr * ss / c)

643 return Degrees(degrees(r), name=N) # C degrees

644

645 A, B, C = _ABC3(useZ, pointA, pointB, pointC)

646 try:

647 sa, sb, sc = map1(radians, alpha, (beta or 0), (gamma or 0))

648 if beta is None:

649 if gamma is None:

650 raise ValueError(_and(Fmt.EQUAL(beta=beta), Fmt.EQUAL(gamma=gamma)))

651 sb = fsumf_(PI2, -sa, -sc)

652 elif gamma is None:

653 sc = fsumf_(PI2, -sa, -sb)

654 else: # subtended angles must add to 360 degrees

655 r = fsum1f_(sa, sb, sc)

656 if fabs(r - PI2) > EPS:

657 raise ValueError(Fmt.EQUAL(sum=degrees(r)))

658 if min(sa, sb, sc) < 0:

659 raise ValueError(_negative_)

660

661 # triangle sides

662 a = B.minus(C).length

663 b = A.minus(C).length

664 c = A.minus(B).length

665

666 ks = [] # 3 Ks and triangle angles

667 dA = _deg_ks(_triAngle(b, c, a), sa, ks, _A_)

668 dB = _deg_ks(_triAngle(a, c, b), sb, ks, _B_)

669 dC = _deg_ks(_triAngle(a, b, c), sc, ks, _C_)

670

671 k = fsum1(ks)

672 if isnear0(k):

673 raise ValueError(Fmt.EQUAL(K=k))

674 x = Fdot(ks, A.x, B.x, C.x).fover(k)

675 y = Fdot(ks, A.y, B.y, C.y).fover(k)

676 z = _zidw(x, y, useZ, A, B, C)

677

678 P = _Clas(tienstra7, pointA, Clas_and_kwds, x, y, z)

679 return Tienstra7Tuple(P, dA, dB, dC, a, b, c, name=typename(tienstra7))

680

681 except (TypeError, ValueError) as x:

682 raise ResectionError(pointA=pointA, pointB=pointB, pointC=pointC,

683 alpha=alpha, beta=beta, gamma=gamma, cause=x)

684

685

686 def triAngle(a, b, c):

687 '''Compute one angle of a triangle.

688

689 @arg a: Adjacent triangle side length (C{scalar}, non-negative

690 C{meter}, conventionally).

691 @arg b: Adjacent triangle side length (C{scalar}, non-negative

692 C{meter}, conventionally).

693 @arg c: Opposite triangle side length (C{scalar}, non-negative

694 C{meter}, conventionally).

695

696 @return: Angle in C{radians} at triangle corner C{C}, opposite

697 triangle side B{C{c}}.

698

699 @raise TriangleError: Invalid or negative B{C{a}}, B{C{b}} or B{C{c}}.

700

701 @see: Functions L{triAngle5} and L{triSide}.

702 '''

703 try:

704 return _triAngle(a, b, c)

705 except (TypeError, ValueError) as x:

706 raise TriangleError(a=a, b=b, c=c, cause=x)

707

708

709 def _triAngle(a, b, c):

710 # (INTERNAL) To allow callers to embellish errors

711 a, b, c = _noneg(a, b, c)

712 if b > a:

713 a, b = b, a

714 if a < EPS0:

715 raise ValueError(_coincident_)

716 b_a = b / a

717 if b_a < EPS0:

718 raise ValueError(_coincident_)

719 t = _Fsumf_(_1_0, b_a**2, -(c / a)**2).fover(b_a * _2_0)

720 return acos1(t)

721

722

723 def triAngle5(a, b, c):

724 '''Compute the angles of a triangle.

725

726 @arg a: Length of the triangle side opposite of triangle corner C{A}

727 (C{scalar}, non-negative C{meter}, conventionally).

728 @arg b: Length of the triangle side opposite of triangle corner C{B}

729 (C{scalar}, non-negative C{meter}, conventionally).

730 @arg c: Length of the triangle side opposite of triangle corner C{C}

731 (C{scalar}, non-negative C{meter}, conventionally).

732

733 @return: L{TriAngle5Tuple}C{(radA, radB, radC, rIn, area)} with angles

734 C{radA}, C{radB} and C{radC} at triangle corners C{A}, C{B}

735 and C{C}, all in C{radians}, the C{InCircle} radius C{rIn}

736 aka C{inradius}, same units as triangle sides B{C{a}},

737 B{C{b}} and B{C{c}} and the triangle C{area} in the same

738 units I{squared}.

739

740 @raise TriangleError: Invalid or negative B{C{a}}, B{C{b}} or B{C{c}}.

741

742 @see: Functions L{triAngle} and L{triArea}.

743 '''

744 try:

745 x, y, z = map1(float, a, b, c)

746 ab = x < y

747 if ab:

748 x, y = y, x

749 bc = y < z

750 if bc:

751 y, z = z, y

752

753 if z > EPS0: # z = min(a, b, c)

754 s = fsum1f_(z, y, x) * _0_5

755 sa, sb, r = (s - x), (s - y), (s - z)

756 r *= _over(sa * sb, s)

757 if r < EPS02:

758 raise ValueError(_coincident_)

759 r = sqrt(r)

760 rA = atan2(r, sa) * _2_0

761 rB = atan2(r, sb) * _2_0

762 rC = fsumf_(PI, -rA, -rB)

763 if min(rA, rB, rC) < 0:

764 raise ValueError(_colinear_)

765 s *= r # Heron's area

766 elif z < 0:

767 raise ValueError(_negative_)

768 else: # 0 <= c <= EPS0

769 rA = rB = PI_2

770 rC = r = s = _0_0

771

772 if bc:

773 rB, rC = rC, rB

774 if ab:

775 rA, rB = rB, rA

776 return TriAngle5Tuple(rA, rB, rC, r, s, name=typename(triAngle5))

777

778 except (TypeError, ValueError) as x:

779 raise TriangleError(a=a, b=b, c=c, cause=x)

780

781

782 def triArea(a, b, c):

783 '''Compute the area of a triangle using U{Heron's<https://

784 WikiPedia.org/wiki/Heron%27s_formula>} C{stable} formula.

785

786 @arg a: Length of the triangle side opposite of triangle corner C{A}

787 (C{scalar}, non-negative C{meter}, conventionally).

788 @arg b: Length of the triangle side opposite of triangle corner C{B}

789 (C{scalar}, non-negative C{meter}, conventionally).

790 @arg c: Length of the triangle side opposite of triangle corner C{C}

791 (C{scalar}, non-negative C{meter}, conventionally).

792

793 @return: The triangle area (C{float}, conventionally C{meter} or

794 same units as B{C{a}}, B{C{b}} and B{C{c}} I{squared}).

795

796 @raise TriangleError: Invalid or negative B{C{a}}, B{C{b}} or B{C{c}}.

797 '''

798 try:

799 r, y, x = sorted(map1(float, a, b, c))

800 if r > 0: # r = min(a, b, c)

801 s = r

802 d = x - y

803 r = (s + d) * (s - d)

804 if r:

805 y += x

806 r *= (y + s) * (y - s)

807 if r < 0:

808 raise ValueError(_negative_)

809 return sqrt(r / _16_0) if r else _0_0

810

811 except (TypeError, ValueError) as x:

812 raise TriangleError(a=a, b=b, c=c, cause=x)

813

814

815 def triSide(a, b, radC):

816 '''Compute one side of a triangle.

817

818 @arg a: Adjacent triangle side length (C{scalar},

819 non-negative C{meter}, conventionally).

820 @arg b: Adjacent triangle side length (C{scalar},

821 non-negative C{meter}, conventionally).

822 @arg radC: Angle included by sides B{C{a}} and B{C{b}},

823 opposite triangle side C{c} (C{radians}).

824

825 @return: Length of triangle side C{c}, opposite triangle

826 corner C{C} and angle B{C{radC}}, same units as

827 B{C{a}} and B{C{b}}.

828

829 @raise TriangleError: Invalid B{C{a}}, B{C{b}} or B{C{radC}}.

830

831 @see: Functions L{sqrt_a}, L{triAngle}, L{triSide2} and L{triSide4}.

832 '''

833 try:

834 return _triSide(a, b, radC)

835 except (TypeError, ValueError) as x:

836 raise TriangleError(a=a, b=b, radC=radC, cause=x)

837

838

839 def _triSide(a, b, radC):

840 # (INTERNAL) To allow callers to embellish errors

841 a, b, r = _noneg(a, b, radC)

842 if b < a:

843 a, b = b, a

844 if a > EPS0:

845 ba = b / a

846 c2 = fsumf_(_1_0, ba**2, _N_2_0 * ba * cos(r))

847 if c2 > EPS02:

848 return a * sqrt(c2)

849 elif c2 < 0:

850 raise ValueError(_invalid_)

851 return hypot(a, b)

852

853

854 def triSide2(b, c, radB):

855 '''Compute a side and its opposite angle of a triangle.

856

857 @arg b: Adjacent triangle side length (C{scalar},

858 non-negative C{meter}, conventionally).

859 @arg c: Adjacent triangle side length (C{scalar},

860 non-negative C{meter}, conventionally).

861 @arg radB: Angle included by sides B{C{a}} and B{C{c}},

862 opposite triangle side C{b} (C{radians}).

863

864 @return: L{TriSide2Tuple}C{(a, radA)} with triangle angle

865 C{radA} in C{radians} and length of the opposite

866 triangle side C{a}, same units as B{C{b}} and B{C{c}}.

867

868 @raise TriangleError: Invalid B{C{b}} or B{C{c}} or either

869 B{C{b}} or B{C{radB}} near zero.

870

871 @see: Functions L{sqrt_a}, L{triSide} and L{triSide4}.

872 '''

873 try:

874 return _triSide2(b, c, radB)

875 except (TypeError, ValueError) as x:

876 raise TriangleError(b=b, c=c, radB=radB, cause=x)

877

878

879 def _triSide2(b, c, radB):

880 # (INTERNAL) To allow callers to embellish errors

881 b, c, rB = _noneg(b, c, radB)

882 sB, cB = sincos2(rB)

883 if isnear0(b) or isnear0(sB):

884 if isnear0(b) and isnear0(sB):

885 if cB < 0:

886 rA = PI

887 a = b + c

888 else:

889 rA = _0_0

890 a = fabs(b - c)

891 else:

892 raise ValueError(_invalid_)

893 else:

894 rC = asin1(c * sB / b)

895 rA = max(fsumf_(PI, -rB, -rC), _0_0)

896 a = sin(rA) * b / sB

897 return TriSide2Tuple(a, rA, name=typename(triSide2))

898

899

900 def triSide4(radA, radB, c):

901 '''Compute two sides and the height of a triangle.

902

903 @arg radA: Angle at triangle corner C{A}, opposite triangle side C{a}

904 (non-negative C{radians}).

905 @arg radB: Angle at triangle corner C{B}, opposite triangle side C{b}

906 (non-negative C{radians}).

907 @arg c: Length of triangle side between triangle corners C{A} and C{B},

908 (C{scalar}, non-negative C{meter}, conventionally).

909

910 @return: L{TriSide4Tuple}C{(a, b, radC, d)} with triangle sides C{a} and

911 C{b} and triangle height C{d} perpendicular to triangle side

912 B{C{c}}, all in the same units as B{C{c}} and interior angle

913 C{radC} in C{radians} at triangle corner C{C}, opposite

914 triangle side B{C{c}}.

915

916 @raise TriangleError: Invalid or negative B{C{radA}}, B{C{radB}} or B{C{c}}.

917

918 @see: U{Triangulation, Surveying<https://WikiPedia.org/wiki/Triangulation_(surveying)>}

919 and functions L{sqrt_a}, L{triSide} and L{triSide2}.

920 '''

921 try:

922 rA, rB, c = _noneg(radA, radB, c)

923 rC = fsumf_(PI, -rA, -rB)

924 if rC < 0:

925 raise ValueError(_negative_)

926 sa, ca, sb, cb = sincos2_(rA, rB)

927 sc = fsum1f_(sa * cb, sb * ca)

928 if sc < EPS0 or min(sa, sb) < 0:

929 raise ValueError(_invalid_)

930 sc = c / sc

931 sa *= sc

932 sd = sa * sb

933 sb *= sc

934 return TriSide4Tuple(sa, sb, rC, sd, name=typename(triSide4))

935

936 except (TypeError, ValueError) as x:

937 raise TriangleError(radA=radA, radB=radB, c=c, cause=x)

938

939

940 def wildberger3(a, b, c, alpha, beta, R3=min):

941 '''Snellius' surveying using U{Rational Trigonometry

942 <https://WikiPedia.org/wiki/Snellius–Pothenot_problem>}.

943

944 @arg a: Length of the triangle side between corners C{B} and C{C} and opposite of

945 triangle corner C{A} (C{scalar}, non-negative C{meter}, conventionally).

946 @arg b: Length of the triangle side between corners C{C} and C{A} and opposite of

947 triangle corner C{B} (C{scalar}, non-negative C{meter}, conventionally).

948 @arg c: Length of the triangle side between corners C{A} and C{B} and opposite of

949 triangle corner C{C} (C{scalar}, non-negative C{meter}, conventionally).

950 @arg alpha: Angle subtended by triangle side B{C{b}} (C{degrees}, non-negative).

951 @arg beta: Angle subtended by triangle side B{C{a}} (C{degrees}, non-negative).

952 @kwarg R3: Callable to determine C{R3} from C{(R3 - C)**2 = D}, typically standard

953 Python function C{min} or C{max}, invoked with 2 arguments, each

954 C{float} or an L{Fsum<pygeodesy.fsums.Fsum>}.

955

956 @return: L{Survey3Tuple}C{(PA, PB, PC)} with distance from survey point C{P} to

957 each of the triangle corners C{A}, C{B} and C{C}, same units as B{C{a}},

958 B{C{b}} and B{C{c}}.

959

960 @raise TriangleError: Invalid B{C{a}}, B{C{b}} or B{C{c}} or negative B{C{alpha}} or

961 B{C{beta}} or B{C{R3}} not C{callable}.

962

963 @see: U{Wildberger, Norman J.<https://Math.Sc.Chula.ac.TH/cjm/content/

964 survey-article-greek-geometry-rational-trigonometry-and-snellius-–-pothenot-surveying>},

965 U{Devine Proportions, page 252<http://www.MS.LT/derlius/WildbergerDivineProportions.pdf>}

966 and function L{snellius3}.

967 '''

968 def _sin2(x):

969 return sin(x)**2

970

971 def _vpa(r3, q2, q3, s2, s3):

972 r4 = r3 * _4_0

973 s4 = r4 * s2

974 if isnear0(s4):

975 raise ValueError(_coincident_)

976 r1 = s2 * q3 / s3

977 N2 = _Fsumf_(r1, r3, -q2)**2

978 n = (r4 * r1 - N2).fover(s4)

979 if n < 0:

980 raise ValueError(_coincident_)

981 return sqrt(n) if n else _0_0

982

983 try:

984 a, b, c, da, db = _noneg(a, b, c, alpha, beta)

985 q1, q2, q3 = q = a**2, b**2, c**2

986 if min(q) < EPS02:

987 raise ValueError(_coincident_)

988

989 ra, rb = map1(radians, da, db)

990 s1, s2, s3 = s = map1(_sin2, rb, ra, ra + rb) # rb, ra!

991 if min(s) < EPS02:

992 raise ValueError(_or(_coincident_, _colinear_))

993 s += _Fsumf_(*s), # == fsum1(s),

994

995 Q = _Fsumf_(*q) # == a**2 + b**2 + ...

996 C0 = Fdot(s, q1, q2, q3, -Q * _0_5)

997 r3 = C0.fover(-s3) # C0 /= -s3

998

999 Q *= Q # Q**2 - 2 * (a**4 + b**4 ...

1000 Q -= hypot2_(*q) * _2_0 # ... + c**4)

1001 Q *= s1 * s2 # Q * s1 * s2 / s3

1002 d0 = Q.fover(s3)

1003 if d0 > EPS02: # > c0

1004 _xcallable(R3=R3)

1005 d0 = sqrt(d0)

1006 r3 = R3(C0 + d0, C0 - d0).as_iscalar # XXX min or max

1007 elif d0 < (-EPS02):

1008 raise ValueError(_negative_)

1009

1010 pa = _vpa(r3, q2, q3, s2, s3)

1011 pb = _vpa(r3, q1, q3, s1, s3)

1012 pc = favg(_triSide2(b, pa, ra).a,

1013 _triSide2(a, pb, rb).a)

1014 return Survey3Tuple(pa, pb, pc, name=typename(wildberger3))

1015

1016 except (TypeError, ValueError) as x:

1017 raise TriangleError(a=a, b=b, c=c, alpha=alpha, beta=beta, R3=R3, cause=x)

1018

1019

1020 def _zidw(x, y, useZ, *ABC):

1021 if useZ: # interpolate z or coplanar with A, B and C?

1022 t = tuple(_.z for _ in ABC)

1023 v = Vector3d(x, y, fmean(t))

1024 z = fidw(t, (v.minus(T).length for T in ABC))

1025 else:

1026 z = INT0

1027 return z

1028

1029# **) MIT License

1030#

1031# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

1032#

1033# Permission is hereby granted, free of charge, to any person obtaining a

1034# copy of this software and associated documentation files (the "Software"),

1035# to deal in the Software without restriction, including without limitation

1036# the rights to use, copy, modify, merge, publish, distribute, sublicense,

1037# and/or sell copies of the Software, and to permit persons to whom the

1038# Software is furnished to do so, subject to the following conditions:

1039#

1040# The above copyright notice and this permission notice shall be included

1041# in all copies or substantial portions of the Software.

1042#

1043# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

1044# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

1045# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

1046# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

1047# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

1048# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

1049# OTHER DEALINGS IN THE SOFTWARE.