1

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

3

4 u'''Wrapper to invoke I{Karney}'s U{GeodSolve

5<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>} utility

6 as an (exact) geodesic, but intended I{for testing purposes only}.

7

8 Set env variable C{PYGEODESY_GEODSOLVE} to the (fully qualified) path

9 of the C{GeodSolve} executable.

10'''

11

12# from pygeodesy.basics import _xinstanceof # from .karney

13# from pygeodesy.errors import _xkwds

14 from pygeodesy.interns import NN, _a12_, _azi1_, _azi2_, \

15 _lat1_, _lat2_, _lon1_, _lon2_, _m12_, \

16 _M12_, _M21_, _s12_, _S12_, _UNDER_

17 from pygeodesy.interns import _UNUSED_, _not_ # PYCHOK used!

18 from pygeodesy.karney import _Azi, Caps, _Deg, GeodesicError, _GTuple, \

19 _Pass, _Lat, _Lon, _M, _M2, _sincos2d, \

20 _xinstanceof, _xkwds

21 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, \

22 _PYGEODESY_GEODSOLVE_, _getenv, printf

23 from pygeodesy.namedTuples import Destination3Tuple, Distance3Tuple

24 from pygeodesy.props import Property, Property_RO

25 from pygeodesy.solveBase import _SolveBase, _SolveLineBase

26 from pygeodesy.utily import _unrollon, _Wrap, wrap360

27

28__all__ = _ALL_LAZY.geodsolve

29__version__ = '23.11.08'

30

31

32 class GeodSolve12Tuple(_GTuple):

33 '''12-Tuple C{(lat1, lon1, azi1, lat2, lon2, azi2, s12, a12, m12, M12, M21, S12)} with

34 angles C{lat1}, C{lon1}, C{azi1}, C{lat2}, C{lon2} and C{azi2} and arc C{a12} all in

35 C{degrees}, initial C{azi1} and final C{azi2} forward azimuths, distance C{s12} and

36 reduced length C{m12} in C{meter}, area C{S12} in C{meter} I{squared} and geodesic

37 scale factors C{M12} and C{M21}, both C{scalar}, see U{GeodSolve

38 <https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}.

39 '''

40 # from GeodSolve --help option -f ... lat1 lon1 azi1 lat2 lon2 azi2 s12 a12 m12 M12 M21 S12

41 _Names_ = (_lat1_, _lon1_, _azi1_, _lat2_, _lon2_, _azi2_, _s12_, _a12_, _m12_, _M12_, _M21_, _S12_)

42 _Units_ = (_Lat, _Lon, _Azi, _Lat, _Lon, _Azi, _M, _Deg, _Pass, _Pass, _Pass, _M2)

43

44

45 class _GeodesicSolveBase(_SolveBase):

46 '''(INTERNAL) Base class for L{GeodesicSolve} and L{GeodesicLineSolve}.

47 '''

48 _Error = GeodesicError

49 _Names_Direct = \

50 _Names_Inverse = GeodSolve12Tuple._Names_

51 _Solve_name = 'GeodSolve'

52 _Solve_path = _getenv(_PYGEODESY_GEODSOLVE_, _PYGEODESY_GEODSOLVE_)

53

54 @Property_RO

55 def _b_option(self):

56 return ('-b',) if self.reverse2 else ()

57

58 @Property_RO

59 def _cmdBasic(self):

60 '''(INTERNAL) Get the basic C{GeodSolve} cmd (C{tuple}).

61 '''

62 return (self.GeodSolve,) + self._b_option \

63 + self._e_option \

64 + self._E_option \

65 + self._p_option \

66 + self._u_option + ('-f',)

67

68 @Property_RO

69 def _E_option(self):

70 return ('-E',) if self.Exact else ()

71

72 @Property

73 def GeodSolve(self):

74 '''Get the U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}

75 executable (C{filename}).

76 '''

77 return self._Solve_path

78

79 @GeodSolve.setter # PYCHOK setter!

80 def GeodSolve(self, path):

81 '''Set the U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}

82 executable (C{filename}), the (fully qualified) path to the C{GeodSolve} executable.

83

84 @raise GeodesicError: Invalid B{C{path}}, B{C{path}} doesn't exist or

85 isn't the C{GeodSolve} executable.

86 '''

87 self._setSolve(path)

88

89 def toStr(self, **prec_sep): # PYCHOK signature

90 '''Return this C{GeodesicSolve} as string.

91

92 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '}

93 for the C{float} C{prec}ision, number of decimal digits

94 (0..9) and the C{sep}arator string to join. Trailing

95 zero decimals are stripped for B{C{prec}} values of

96 1 and above, but kept for negative B{C{prec}} values.

97

98 @return: GeodesicSolve items (C{str}).

99 '''

100 return _SolveBase._toStr(self, GeodSolve=self.GeodSolve, **prec_sep)

101

102 @Property_RO

103 def _u_option(self):

104 return ('-u',) if self.unroll else ()

105

106

107 class GeodesicSolve(_GeodesicSolveBase):

108 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}

109 as an C{Exact} version of I{Karney}'s Python class U{Geodesic<https://GeographicLib.SourceForge.io/C++/doc/

110 python/code.html#geographiclib.geodesic.Geodesic>}.

111

112 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully

113 qualified) path to the C{GeodSolve} executable.

114

115 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve}

116 executable for I{every} method call.

117 '''

118

119 def Area(self, polyline=False, name=NN):

120 '''Set up a L{GeodesicAreaExact} to compute area and

121 perimeter of a polygon.

122

123 @kwarg polyline: If C{True} perimeter only, otherwise

124 area and perimeter (C{bool}).

125 @kwarg name: Optional name (C{str}).

126

127 @return: A L{GeodesicAreaExact} instance.

128

129 @note: The B{C{debug}} setting is passed as C{verbose}

130 to the returned L{GeodesicAreaExact} instance.

131 '''

132 gaX = _MODS.geodesicx.GeodesicAreaExact(self, polyline=polyline,

133 name=name or self.name)

134 if self.verbose or self.debug: # PYCHOK no cover

135 gaX.verbose = True

136 return gaX

137

138 Polygon = Area # for C{geographiclib} compatibility

139

140 def Direct3(self, lat1, lon1, azi1, s12): # PYCHOK outmask

141 '''Return the destination lat, lon and reverse azimuth

142 (final bearing) in C{degrees}.

143

144 @return: L{Destination3Tuple}C{(lat, lon, final)}.

145 '''

146 r = self._GDictDirect(lat1, lon1, azi1, False, s12, floats=False)

147 return Destination3Tuple(float(r.lat2), float(r.lon2), wrap360(r.azi2),

148 iteration=r._iteration)

149

150 def _DirectLine(self, ll1, azi12, **caps_name): # PYCHOK no cover

151 '''(INTERNAL) Short-cut version.

152 '''

153 return self.DirectLine(ll1.lat, ll1.lon, azi12, **caps_name)

154

155 def DirectLine(self, lat1, lon1, azi1, **caps_name):

156 '''Set up a L{GeodesicLineSolve} to compute several points

157 on a single geodesic.

158

159 @arg lat1: Latitude of the first point (C{degrees}).

160 @arg lon1: Longitude of the first point (C{degrees}).

161 @arg azi1: Azimuth at the first point (compass C{degrees}).

162 @kwarg caps_name: Bit-or'ed combination of L{Caps} values specifying

163 the capabilities the L{GeodesicLineSolve} instance should

164 possess, C{caps=Caps.ALL} always.

165

166 @return: A L{GeodesicLineSolve} instance.

167

168 @note: If the point is at a pole, the azimuth is defined by keeping

169 B{C{lon1}} fixed, writing C{B{lat1} = ±(90 − ε)}, and taking

170 the limit C{ε → 0+}.

171

172 @see: C++ U{GeodesicExact.Line

173 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>}

174 and Python U{Geodesic.Line<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.

175 '''

176 return GeodesicLineSolve(self, lat1, lon1, azi1, **_xkwds(caps_name, name=self.name))

177

178 Line = DirectLine

179

180 def _Inverse(self, ll1, ll2, wrap, **outmask): # PYCHOK no cover

181 '''(INTERNAL) Short-cut version, see .ellipsoidalBaseDI.intersecant2.

182 '''

183 if wrap:

184 ll2 = _unrollon(ll1, _Wrap.point(ll2))

185 return self.Inverse(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **outmask)

186

187 def Inverse3(self, lat1, lon1, lat2, lon2): # PYCHOK outmask

188 '''Return the distance in C{meter} and the forward and

189 reverse azimuths (initial and final bearing) in C{degrees}.

190

191 @return: L{Distance3Tuple}C{(distance, initial, final)}.

192 '''

193 r = self._GDictInverse(lat1, lon1, lat2, lon2, floats=False)

194 return Distance3Tuple(float(r.s12), wrap360(r.azi1), wrap360(r.azi2),

195 iteration=r._iteration)

196

197 def _InverseLine(self, ll1, ll2, wrap, **caps_name): # PYCHOK no cover

198 '''(INTERNAL) Short-cut version.

199 '''

200 if wrap:

201 ll2 = _unrollon(ll1, _Wrap.point(ll2))

202 return self.InverseLine(ll1.lat, ll1.lon, ll2.lat, ll2.lon, **caps_name)

203

204 def InverseLine(self, lat1, lon1, lat2, lon2, **caps_name): # PYCHOK no cover

205 '''Set up a L{GeodesicLineSolve} to compute several points

206 on a single geodesic.

207

208 @arg lat1: Latitude of the first point (C{degrees}).

209 @arg lon1: Longitude of the first point (C{degrees}).

210 @arg lat2: Latitude of the second point (C{degrees}).

211 @arg lon2: Longitude of the second point (C{degrees}).

212 @kwarg caps_name: Bit-or'ed combination of L{Caps} values specifying

213 the capabilities the L{GeodesicLineSolve} instance should

214 possess, C{caps=Caps.ALL} always.

215

216 @return: A L{GeodesicLineSolve} instance.

217

218 @note: Both B{C{lat1}} and B{C{lat2}} should in the range C{[-90, +90]}.

219

220 @see: C++ U{GeodesicExact.InverseLine

221 <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicExact.html>} and

222 Python U{Geodesic.InverseLine<https://GeographicLib.SourceForge.io/Python/doc/code.html>}.

223 '''

224 r = self.Inverse(lat1, lon1, lat2, lon2)

225 return GeodesicLineSolve(self, lat1, lon1, r.azi1, **_xkwds(caps_name, name=self.name))

226

227

228 class GeodesicLineSolve(_GeodesicSolveBase, _SolveLineBase):

229 '''Wrapper to invoke I{Karney}'s U{GeodSolve<https://GeographicLib.SourceForge.io/C++/doc/GeodSolve.1.html>}

230 as an C{Exact} version of I{Karney}'s Python class U{GeodesicLine<https://GeographicLib.SourceForge.io/C++/doc/

231 python/code.html#geographiclib.geodesicline.GeodesicLine>}.

232

233 @note: Use property C{GeodSolve} or env variable C{PYGEODESY_GEODSOLVE} to specify the (fully

234 qualified) path to the C{GeodSolve} executable.

235

236 @note: This C{geodesic} is intended I{for testing purposes only}, it invokes the C{GeodSolve}

237 executable for I{every} method call.

238 '''

239

240 def __init__(self, geodesic, lat1, lon1, azi1, caps=Caps.ALL, name=NN):

241 '''New L{GeodesicLineSolve} instance, allowing points to be found along

242 a geodesic starting at C{(B{lat1}, B{lon1})} with azimuth B{C{azi1}}.

243

244 @arg geodesic: The geodesic to use (L{GeodesicSolve}).

245 @arg lat1: Latitude of the first point (C{degrees}).

246 @arg lon1: Longitude of the first point (C{degrees}).

247 @arg azi1: Azimuth at the first points (compass C{degrees}).

248 @kwarg caps: Bit-or'ed combination of L{Caps} values specifying

249 the capabilities the L{GeodesicLineSolve} instance

250 should possess, always C{Caps.ALL}. Use C{Caps.LINE_OFF}

251 if updates to the B{C{geodesic}} should I{not} be

252 reflected in this L{GeodesicLineSolve} instance.

253 @kwarg name: Optional name (C{str}).

254

255 @raise GeodesicError: Invalid path for the C{GeodSolve} executable or

256 or isn't the C{GeodSolve} executable, see

257 property C{geodesic.GeodSolve}.

258

259 @raise TypeError: Invalid B{C{geodesic}}.

260 '''

261 _xinstanceof(GeodesicSolve, geodesic=geodesic)

262 if (caps & Caps.LINE_OFF): # copy to avoid updates

263 geodesic = geodesic.copy(deep=False, name=NN(_UNDER_, geodesic.name))

264 _SolveLineBase.__init__(self, geodesic, lat1, lon1, caps, name, azi1=azi1)

265 try:

266 self.GeodSolve = geodesic.GeodSolve # geodesic or copy of geodesic

267 except GeodesicError:

268 pass

269

270 def ArcPosition(self, a12, outmask=_UNUSED_): # PYCHOK unused

271 '''Find the position on the line given B{C{a12}}.

272

273 @arg a12: Spherical arc length from the first point to the

274 second point (C{degrees}).

275

276 @return: A C{GDict} with 12 items C{lat1, lon1, azi1, lat2, lon2,

277 azi2, m12, a12, s12, M12, M21, S12}.

278 '''

279 return self._GDictInvoke(self._cmdArc, True, self._Names_Direct, a12)

280

281 @Property_RO

282 def azi1(self):

283 '''Get the azimuth at the first point (compass C{degrees}).

284 '''

285 return self._lla1.azi1

286

287 azi12 = azi1 # like RhumbLineSolve

288

289 @Property_RO

290 def azi1_sincos2(self):

291 '''Get the sine and cosine of the first point's azimuth (2-tuple C{(sin, cos)}).

292 '''

293 return _sincos2d(self.azi1)

294

295 azi12_sincos2 = azi1_sincos2

296

297 @Property_RO

298 def _cmdArc(self):

299 '''(INTERNAL) Get the C{GeodSolve} I{-a -L} cmd (C{tuple}).

300 '''

301 return self._cmdDistance + ('-a',)

302

303 def Intersecant2(self, lat0, lon0, radius, **kwds): # PYCHOK no cover

304 '''B{Not implemented}, throws a C{NotImplementedError} always.'''

305 _MODS.named.notImplemented(self, lat0, lon0, radius, **kwds)

306

307 def PlumbTo(self, lat0, lon0, **kwds): # PYCHOK no cover

308 '''B{Not implemented}, throws a C{NotImplementedError} always.'''

309 _MODS.named.notImplemented(self, lat0, lon0, **kwds)

310

311 def Position(self, s12, outmask=_UNUSED_): # PYCHOK unused

312 '''Find the position on the line given B{C{s12}}.

313

314 @arg s12: Distance from the first point to the second (C{meter}).

315

316 @return: A C{GDict} with 12 items C{lat1, lon1, azi1, lat2, lon2,

317 azi2, m12, a12, s12, M12, M21, S12}, possibly C{a12=NAN}.

318 '''

319 return self._GDictInvoke(self._cmdDistance, True, self._Names_Direct, s12)

320

321 def toStr(self, **prec_sep): # PYCHOK signature

322 '''Return this C{GeodesicLineSolve} as string.

323

324 @kwarg prec_sep: Keyword argumens C{B{prec}=6} and C{B{sep}=', '}

325 for the C{float} C{prec}ision, number of decimal digits

326 (0..9) and the C{sep}arator string to join. Trailing

327 zero decimals are stripped for B{C{prec}} values of

328 1 and above, but kept for negative B{C{prec}} values.

329

330 @return: GeodesicLineSolve items (C{str}).

331 '''

332 return _SolveLineBase._toStr(self, azi1=self.azi1, geodesic=self._solve,

333 GeodSolve=self.GeodSolve, **prec_sep)

334

335

336__all__ += _ALL_DOCS(_GeodesicSolveBase)

337

338 if __name__ == '__main__':

339

340 from sys import argv

341

342 gS = GeodesicSolve(name='Test')

343 gS.verbose = '--verbose' in argv # or '-v' in argv

344

345 if gS.GeodSolve in (_PYGEODESY_GEODSOLVE_, None): # not set

346 gS.GeodSolve = '/opt/local/bin/GeodSolve' # '/opt/local/Cellar/geographiclib/1.51/bin/GeodSolve' # HomeBrew

347 printf('version: %s', gS.version)

348

349 r = gS.Direct(40.6, -73.8, 51, 5.5e6)

350 printf('Direct: %r', r, nl=1)

351 printf('Direct3: %r', gS.Direct3(40.6, -73.8, 51, 5.5e6))

352

353 printf('Inverse: %r', gS.Inverse( 40.6, -73.8, 51.6, -0.5), nl=1)

354 printf('Inverse1: %r', gS.Inverse1(40.6, -73.8, 51.6, -0.5))

355 printf('Inverse3: %r', gS.Inverse3(40.6, -73.8, 51.6, -0.5))

356

357 glS = GeodesicLineSolve(gS, 40.6, -73.8, 51, name='LineTest')

358 p = glS.Position(5.5e6)

359 printf('Position: %s %r', p == r, p, nl=1)

360 p = glS.ArcPosition(49.475527)

361 printf('ArcPosition: %s %r', p == r, p)

362

363# % python3 -m pygeodesy.geodsolve

364

365# version: /opt/local/bin/GeodSolve: GeographicLib version 1.51

366

367# version: /opt/local/bin/GeodSolve: GeographicLib version 1.51

368

369# Direct: GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0)

370# Direct3: Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397)

371

372# Inverse: GDict(M12=0.64473, M21=0.645046, S12=40041368848742.53125, a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, s12=5551759.400319)

373# Inverse1: 49.94131021789904

374# Inverse3: Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777)

375

376# Position: True GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0)

377# ArcPosition: False GDict(M12=0.650911, M21=0.651229, S12=39735074737272.734375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, m12=4844148.669561, s12=5499999.948497)

378

379

380# % python3 -m pygeodesy.geodsolve --verbose

381

382# GeodesicSolve 'Test' 1: /opt/local/bin/GeodSolve --version (invoke)

383# GeodesicSolve 'Test' 1: /opt/local/bin/GeodSolve: GeographicLib version 1.51 (0)

384# version: /opt/local/bin/GeodSolve: GeographicLib version 1.51

385# GeodesicSolve 'Test' 2: /opt/local/bin/GeodSolve -E -p 10 -f \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct)

386# GeodesicSolve 'Test' 2: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200829, azi2=107.189397162605886, s12=5500000.0, a12=49.475527463251467, m12=4844148.703101486, M12=0.65091056699808603, M21=0.65122865892196558, S12=39735075134877.094 (0)

387

388# Direct: GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0)

389# GeodesicSolve 'Test' 3: /opt/local/bin/GeodSolve -E -p 10 -f \ 40.600000000000001 -73.799999999999997 51.0 5500000.0 (Direct3)

390# GeodesicSolve 'Test' 3: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.0, lat2=51.884564505606761, lon2=-1.141172861200829, azi2=107.189397162605886, s12=5500000.0, a12=49.475527463251467, m12=4844148.703101486, M12=0.65091056699808603, M21=0.65122865892196558, S12=39735075134877.094 (0)

391# Direct3: Destination3Tuple(lat=51.884565, lon=-1.141173, final=107.189397)

392# GeodesicSolve 'Test' 4: /opt/local/bin/GeodSolve -E -p 10 -f -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse)

393# GeodesicSolve 'Test' 4: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186841, a12=49.941310217899037, m12=4877684.6027061976, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0)

394

395# Inverse: GDict(M12=0.64473, M21=0.645046, S12=40041368848742.53125, a12=49.94131, azi1=51.198883, azi2=107.821777, lat1=40.6, lat2=51.6, lon1=-73.8, lon2=-0.5, m12=4877684.602706, s12=5551759.400319)

396# GeodesicSolve 'Test' 5: /opt/local/bin/GeodSolve -E -p 10 -f -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse1)

397# GeodesicSolve 'Test' 5: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186841, a12=49.941310217899037, m12=4877684.6027061976, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0)

398# Inverse1: 49.94131021789904

399# GeodesicSolve 'Test' 6: /opt/local/bin/GeodSolve -E -p 10 -f -i \ 40.600000000000001 -73.799999999999997 51.600000000000001 -0.5 (Inverse3)

400# GeodesicSolve 'Test' 6: lat1=40.600000000000001, lon1=-73.799999999999997, azi1=51.198882845579824, lat2=51.600000000000001, lon2=-0.5, azi2=107.821776735514248, s12=5551759.4003186841, a12=49.941310217899037, m12=4877684.6027061976, M12=0.64472969205948238, M21=0.64504567852134398, S12=40041368848742.531 (0)

401# Inverse3: Distance3Tuple(distance=5551759.400319, initial=51.198883, final=107.821777)

402

403# Position: True GDict(M12=0.650911, M21=0.651229, S12=39735075134877.09375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141173, m12=4844148.703101, s12=5500000.0)

404# ArcPosition: False GDict(M12=0.650911, M21=0.651229, S12=39735074737272.734375, a12=49.475527, azi1=51.0, azi2=107.189397, lat1=40.6, lat2=51.884565, lon1=-73.8, lon2=-1.141174, m12=4844148.669561, s12=5499999.948497)

405

406

407# **) MIT License

408#

409# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.

410#

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

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

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

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

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

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

417#

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

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

420#

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

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

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

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

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

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

427# OTHER DEALINGS IN THE SOFTWARE.