Coverage for pyrdnap / rd0.py: 98%

190 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-06-10 10:52 -0400

1 

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

3 

4u'''(INTERNAL) RijksDriehoeksmeting C{_RD} and reference C{_RD0} 

5constants and classes C{RDNAP7Tuple} and C{LqRD}. 

6''' 

7# make sure int/int division yields float quotient, see .basics 

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

9 

10from pyrdnap.v_grids import _v_assert 

11from pyrdnap.__pygeodesy import (_0_0, _0_5, _1_0, _2_0, # PYCHOK used! 

12 _isNAN, _isNAN0, _xinstanceof, _xsubclassof, 

13 _LLEB, _xkwds, 

14 _COMMASPACE_, _datum_, _lat_, _lon_, _height_, 

15 _ALL_OTHER, _FOR_DOCS, _Pass, _NamedTuple) 

16from pygeodesy import (NAN, NN, map1, map2, # basics, "consterns" 

17 Datum, Datums, Similarity, # datums 

18 Bounds4Tuple, LatLon2Tuple, PhiLam2Tuple, # namedTuples 

19 Vector2Tuple, Vector3Tuple, LqRD as _LqRD, # ltp 

20 Property_RO, property_ROnce, # props 

21 pairs, # streprs 

22 Height, Lamd, Lat, Lon, Meter, Phi, Phid, # units 

23 sincos2, tanPI_2_2) # utily 

24 

25from math import atan2, ceil, fabs, floor, log, sin, sqrt 

26 

27__all__ = () 

28__version__ = '26.06.08' 

29 

30_LQRD = _LqRD() # get Amersfoort, bounds, etc. (deleted below) 

31 

32 

33def _c_f_N_f3(*deg_SW_D): 

34 # return int(ceil) and int(floor) of Normalized 

35 # and (Normalized less floor) of C{deg} degrees 

36 N = _degN(*deg_SW_D) 

37 # assert N >= 0, N 

38 f = floor(N) 

39 return int(ceil(N)), int(f), (N - f) 

40 

41 

42def _degN(deg, degSW, deg_D): 

43 # return C{deg} Normalized 

44 return (deg - degSW) * deg_D 

45 

46 

47class _RDbase(object): 

48 '''(INTERNAL) Base. 

49 ''' 

50 def _preDict(self, _pred, **d): 

51 # return updated dict C{d} 

52 for n in self.__class__.__dict__.keys(): 

53 if _pred(n): 

54 d[n] = getattr(self, n) 

55 return d 

56 

57 def toStr(self, prec=9, **fmt_ints): 

58 # return this C{_RDx} as string 

59 d = self._toDict() # PYCHOK OK 

60 t = pairs(d, prec=prec, **fmt_ints) 

61 return _COMMASPACE_(*t) 

62 

63 

64class _RD(_RDbase): 

65 '''(INTERNAL) Bounds, constants for RDNAP2018 (ASCII.txt). 

66 ''' 

67 lat_D = Lat(lat_D=80.0) # == 1 / 0.0125 # degrees, all 

68 lon_D = Lon(lon_D=50.0) # == 1 / 0.02 

69 

70 def __init__(self): 

71 S, W, N, E = self.region 

72 nlat = _degN(N, S, self.lat_D) + _1_0 # 2.3.2g n-phi 

73 nlon = _degN(E, W, self.lon_D) + _1_0 # 2.3.2g n-lambda 

74 _v_assert(map1(int, nlat, nlon)) 

75 

76 def _c_f_N_f6(self, lat, lon): 

77 # return (int(ceil), int(floor), Normalized less floor) of C{lat}) + \ 

78 # (int(ceil), int(floor), Normalized less floor) of C{lon}) 

79 return _c_f_N_f3(lat, self.region.latS, self.lat_D) + \ 

80 _c_f_N_f3(lon, self.region.lonW, self.lon_D) 

81 

82 def isinside(self, lat, lon, as89=False, eps=0): # eps=_TOL_D, 0 or -_TOLD_D 

83 # is C{(lat, lon)} inside the this C{RD} region[89], optionally 

84 # over-/undersized by positive respectively negative C{eps}? 

85 S, W, N, E = self.region89 if as89 else self.region 

86 # XXX use "< N" and "< E" instead of "<="? 

87 return ((S - lat) <= eps and (lat - N) <= eps and 

88 (W - lon) <= eps and (lon - E) <= eps) if eps else \ 

89 (S <= lat <= N and W <= lon <= E) 

90 

91 region = _LQRD.region # as RD-Bessel L{Bounds4Tuple} 

92 

93 @property_ROnce 

94 def region89(self): 

95 '''Get the C{RD} region as ETRS89 (GRS80) (L{Bounds4Tuple}). 

96 ''' 

97 from pyrdnap.rdnap2018 import _RDNAPbase as _R 

98 return _R().region89 

99 

100 def _toDict(self): 

101 def _p(n): # lambda 

102 return n.endswith('D') or n.endswith('S') 

103 

104 return self._preDict(_p, region=self.region, region89=self.region89) 

105 

106 @property_ROnce 

107 def _xETRS2RD(self): # transform ETRS (GRS80) to RD-Bessel 

108 return Similarity(tx=-565.7346, ty=-50.4058, tz=-465.2895, s=-4.07242, 

109 rx=-1.91513, ry=1.60365, rz=-9.09546, name='_xETRS2RD') 

110 

111 @property_ROnce 

112 def _xRD2ETRS(self): # transform RD-Bessel to ETRS (GRS80) 

113 return Similarity(tx=565.7381, ty=50.4018, tz=465.2904, s=4.07244, 

114 rx=1.91514, ry=-1.60363, rz=9.09546, name='_xRD2ETRS') 

115 

116 # % python -c "import pyrdnap; print(pyrdnap.rd0._RD.toStr())" 

117 # _xETRS2RD=Similarity(name='_xETRS2RD', tx=-565.73, ty=-50.406, tz=-465.29, s=-4.0724, 

118 # rx=-1.9151, ry=1.6037, rz=-9.0955), 

119 # _xRD2ETRS=Similarity(name='_xRD2ETRS', tx=565.74, ty=50.402, tz=465.29, s=4.0724, 

120 # rx=1.9151, ry=-1.6036, rz=9.0955), 

121 # lat_D=80.0, lon_D=50.0, 

122 # region=RD region (latS=50.0, lonW=2.0, latN=56.0, lonE=8.0), 

123 # region89=RD region89 (latS=49.999276, lonW=2.000032, latN=55.998561, lonE=7.999158) 

124 

125_RD = _RD() # PYCHOK singleton, in .test/testRndTrips 

126 

127 

128class _RD0(_RDbase): 

129 '''(INTERNAL) C{RD} Amersfoort, NL / C{RD New} constants for RDNAP2018 (ASCII.txt). 

130 

131 @see: U{EPSG:9809<https://EPSG.io/9809-method>}, U{"Oblique Stereographic" 

132 <https://PROJ.org/en/stable/operations/projections/sterea.html>} and 

133 <http://geotiff.maptools.org/proj_list/oblique_stereographic.html> 

134 ''' 

135 H0 = Meter(H0 =_LQRD.height0) # Amersfoort.height0 0.0 m 

136 H0_ETRS = Meter(H0_ETRS=_LQRD.height0_ETRS) # 43.0 m 

137 K0 = 0.9999079 # 2.4.1 scale factor 

138 LAT0 = Lat(LAT0=_LQRD.Amersfoort.lat) # '52 9 22.178N' == 52.156160555555+° 

139 LON0 = Lon(LON0=_LQRD.Amersfoort.lon) # ' 5 23 15.5E' == 5.387638888888+° 

140 LAM0C = \ 

141 LAM0 = Lamd(LAM0=LON0) # 𝜆0, 𝛬0 = 𝜆0 on sphere 0.094032038 

142 PHI0 = Phid(PHI0=LAT0) # 𝜑0 0.910296727, PHI0C 𝛷0 set below 

143 X0 = Meter(X0=155000.0) # false Easting 155029.784? 

144 Y0 = Meter(Y0=463000.0) # false Norting 463109.889? 

145 

146# @property_ROnce 

147# def C0(self): # c, sphere 

148# s, _ = self.sincos2PHI0 

149# w = self._w1(s) 

150# c = (w - _1_0) / (w + _1_0) 

151# return (((self.N0 + s) * (_1_0 - c)) / 

152# ((self.N0 - s) * (_1_0 + c))) 

153 

154# def chilam(self, lat, lon): # EPSG:9809 

155# # return 2-tuple (chi, lam), conformal in radians 

156# s, _ = sincos2d(lat) 

157# w2 = self._w1(s) * self.C0 

158# s = (w2 - _1_0) / (w2 + _1_0) 

159# r = radians(lon - self.LON0) * self.N0 

160# return asin(s), r 

161 

162 @property_ROnce 

163 def D0(self): # lazily 

164 return Datums.Bessel1841 

165 

166 @property_ROnce 

167 def D80(self): # lazily 

168 return Datums.GRS80 

169 

170 @property_ROnce 

171 def E0(self): # lazily 

172 return self.D0.ellipsoid 

173 

174 def log_e_2(self, phi): 

175 e = self.E0.e 

176 p = e * sin(phi) 

177 return log((_1_0 + p) / (_1_0 - p)) * (e * _0_5) 

178 

179 def log_tan(self, phi): 

180 return log(tanPI_2_2(phi)) # tan((phi + PI/2) / 2) 

181 

182 @property_ROnce 

183 def M0(self): # 2.4.1 p 15 m 

184 return self.W0 - self.N0 * self.Q0 

185 

186 @property_ROnce 

187 def N0(self): # 2.4.1 p 15 n, sphere 

188 E = self.E0 

189 _, c = self.sincos2PHI0 

190 return sqrt(c**4 * E.e2 / E.e21 + _1_0) 

191 

192 @property_ROnce 

193 def PHI0C(self): # 2.4.1 p 15 𝛷0, Amersfoort latitude on sphere 

194 m, n = self.Rmn2 

195 s, c = self.sincos2PHI0 

196 return Phi(PHI0C=atan2(m * s, n * c)) # atan((m / n) * tan(PHI0)) 

197 

198 @property_ROnce 

199 def Q0(self): # 2.4.1 p 15 q0 

200 return self.log_tan(self.PHI0) - self.log_e_2(self.PHI0) 

201 

202 @property_ROnce 

203 def R(self): # 2.4.1 p 15 R, radius conformal sphere 

204 m, n = self.Rmn2 

205 return m * n 

206 

207 @property_ROnce 

208 def RK2(self): # 2.4.2 

209 return self.R * self.K0 * _2_0 

210 

211 @property_ROnce 

212 def Rmn2(self): # 2.4.1 p 15 (sqrt(RsubM), sqrt(RsubN)) 

213 # RsubM, RsubN == RHO0, NU0 EPSG:9809 

214 E = self.E0 

215 s, _ = self.sincos2PHI0 

216 s = _1_0 - s**2 * E.e2 

217 # assert s > 0 

218 N = E.a / sqrt(s) 

219 # assert N > 0 

220 M = E.e21 * N / s 

221 # assert M > 0 

222 return map1(sqrt, M, N) # sqrt! 

223 

224 @property_ROnce 

225 def sincos2PHI0(self): # 𝜑0 

226 return sincos2(self.PHI0) 

227 

228 @property_ROnce 

229 def sincos2PHI0C(self): # 𝛷0 

230 return sincos2(self.PHI0C) 

231 

232 def _toDict(self): 

233 def _p(n): # lambda 

234 return n.endswith('0') or n.startswith('R') or \ 

235 n.endswith('0C') # _0_ 

236 

237 return self._preDict(_p, H0_ETRS=self.H0_ETRS) 

238 

239 @property_ROnce 

240 def W0(self): # 2.4.1 p 15 w0 

241 return self.log_tan(self.PHI0C) # 𝛷0 

242 

243# def _w1(self, sphi): # EPSG:9809 

244# w1 = NAN 

245# if _1_0 > sphi > _N_1_0: 

246# e = self.E0.e 

247# S = (_1_0 + sphi) / (_1_0 - sphi) 

248# T = (_1_0 - sphi * e) / (_1_0 + sphi * e) 

249# w1 = pow(pow(T, e) * S, self.N0) 

250# return w1 

251 

252 # % python -c "import pyrdnap; print(pyrdnap.rd0._RD0.toStr())" 

253 # D0=Datum(name='Bessel1841', ellipsoid=Ellipsoids.Bessel1841, transform=Transforms.Bessel1841), 

254 # D80=Datum(name='GRS80', ellipsoid=Ellipsoids.GRS80, transform=Transforms.WGS84), 

255 # E0=Ellipsoid(name='Bessel1841', a=6377397.155, f=0.00334277, f_=299.1528128, b=6356078.962818), 

256 # H0=0.0, H0_ETRS=43.0, K0=0.9999079, LAM0=0.094032038, LAM0C=0.094032038, 

257 # LAT0=52.156160556, LON0=5.387638889, M0=0.003773954, N0=1.000475857, 

258 # PHI0=0.910296727, PHI0C=0.909684757, Q0=1.06531844, 

259 # R=6382644.571035411, RK2=12764113.458940838, Rmn2=(2524.794785679199, 2527.9854850929623), 

260 # sincos2PHI0=(0.7896858198001045, 0.6135114554811807), 

261 # sincos2PHI0C=(0.7893102212553742, 0.6139946047171686), 

262 # W0=1.069599332, X0=155000.0, Y0=463000.0 

263 

264_RD0 = _RD0() # PYCHOK singleton, in .test/testRndTrips 

265 

266 

267class RDNAP7Tuple(_NamedTuple): # in .v_self 

268 '''7-Tuple C{(RDx, RDy, NAPh, lat, lon, height, datum)} with I{local} C{RDx}, 

269 C{RDy} and C{NAPh} quasi-geoid_height, geodetic C{lat}, C{lon}, C{height} 

270 and C{datum} with C{lat} and C{lon} in C{degrees} and with C{RDx}, C{RDy}, 

271 C{NAPh} and C{height} in C{meter}, conventionally. 

272 

273 @note: The C{lat} and {lon} are I{by default} B{GRS80 (ETRS89)} geodetic 

274 coordinates L{RDNAP2018v1.reverse} but B{Bessel1841 (RD-Bessel)} 

275 when returned from L{RDNAP2018v2.reverse}. 

276 ''' 

277 _Names_ = ('RDx', 'RDy', 'NAPh', _lat_, _lon_, _height_, _datum_) 

278 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass) 

279 

280 @property_ROnce 

281 def _datum_index(self): 

282 return self._Names_.index(_datum_) 

283 

284 def diff(self, other, datum=None, **name): 

285 '''Return the difference between this and an C{other} C{RDNAP7Tuple}. 

286 

287 @kwarg datum: Optional difference C{B{datum}=None} (C{Latum}). 

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

289 

290 @return: An L{RDNAP7Tuple} with the C{_diff} for each item, but 

291 C{datum=B{datum}}. 

292 ''' 

293 def _diff(a, b): 

294 return fabs(a - b) 

295 

296 _xinstanceof(RDNAP7Tuple, other=other) 

297 d = self._datum_index 

298 t = map2(_diff, self[:d], other[:d]) 

299 return RDNAP7Tuple(t + (datum,), **name) 

300 

301 @Property_RO 

302 def lam(self): 

303 '''Get the longitude (B{C{radians}}). 

304 ''' 

305 return Lamd(self.lon) # PYCHOK lon 

306 

307 @Property_RO 

308 def latlon(self): 

309 '''Get the lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}). 

310 ''' 

311 return LatLon2Tuple(self.lat, self.lon, name=self.name) 

312 

313 @Property_RO 

314 def latlonheight(self): 

315 '''Get the lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}). 

316 ''' 

317 return self.latlon.to3Tuple(self.height) 

318 

319 @Property_RO 

320 def latlonheightdatum(self): 

321 '''Get the lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}). 

322 ''' 

323 return self.latlonheight.to4Tuple(self.datum) 

324 

325 @Property_RO 

326 def phi(self): 

327 '''Get the latitude (B{C{radians}}). 

328 ''' 

329 return Phid(self.lat) # PYCHOK lat 

330 

331 @Property_RO 

332 def philam(self): 

333 '''Get the lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}). 

334 ''' 

335 return PhiLam2Tuple(self.phi, self.lam, name=self.name) # PYCHOK lam, phi 

336 

337 @Property_RO 

338 def philamheight(self): 

339 '''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}). 

340 ''' 

341 return self.philam.to3Tuple(self.height) # PYCHOK height 

342 

343 @Property_RO 

344 def philamheightdatum(self): 

345 '''Get the lat-, longitude in C{radians} with height and datum (L{PhiLamn4Tuple}C{(phi, lam, height, datum)}). 

346 ''' 

347 return self.philamheight.to4Tuple(self.datum) 

348 

349 def toDatum(self, datum2, **name): 

350 '''Convert this C{lat}, C{lon} and C{height} to B{C{datum2}}. 

351 

352 @arg datum2: Datum to convert I{to} (L{Datum}). 

353 

354 @return: An L{RDNAP7Tuple} with converted C{lat}, C{lon} and C{height} 

355 or this L{RDNAP7Tuple} if this.datum is B{C{datum2}}. 

356 ''' 

357 _xinstanceof(Datum, datum2=datum2) 

358 if self.datum is datum2: # PYCHOK or self.datum == datum2 

359 return self 

360 g = self.toLatLon(_LLEB).toDatum(datum2) 

361 h = NAN if _isNAN(self.height) else g.height # PYCHOK preserve height NAN 

362 return self.dup(lat=g.lat, lon=g.lon, datum=g.datum, height=h, 

363 **_xkwds(name, name=self.name)) 

364 

365 def toLatLon(self, LatLon, **LatLon_kwds): 

366 '''Return this C{lat}, C{lon}, C{datum} and C{height} as B{C{LatLon}}. 

367 

368 @arg LatLon: An ellipsodial C{LatLon} class (C{pygeodesy.ellipsoidal*}). 

369 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments. 

370 

371 @return: An B{C{LatLon}} instance. 

372 

373 @raise TypeError: B{C{LatLon}} not ellipsoidal or an other issue. 

374 ''' 

375 _xsubclassof(_LLEB, LatLon=LatLon) 

376 h = _isNAN0(self.height) # PYCHOK height 

377 kwds = _xkwds(LatLon_kwds, name=self.name, height=h) 

378 return LatLon(self.lat, self.lon, datum=self.datum, **kwds) # PYCHOK datum 

379 

380 @Property_RO 

381 def xy(self): 

382 '''Get the I{local} C{(RDx, RDy)} coordinates (L{Vector2Tuple}C{(x, y)}). 

383 ''' 

384 return Vector2Tuple(self.RDx, self.RDy, name=self.name) 

385 

386 @Property_RO 

387 def xyz(self): 

388 '''Get the I{local} C{(RDx, RDy, NAPh)} coordinates and height (L{Vector3Tuple}C{(x, y, z)}). 

389 ''' 

390 return Vector3Tuple(self.RDx, self.RDy, self.NAPh, name=self.name) 

391 

392 

393class LqRD(_LqRD): 

394 '''Like U{pygeodesy.LqRD<https://mrJean1.GitHub.io/PyGeodesy/docs/pygeodesy.ltp.LqRD-class.html>} 

395 but with methods C{forward} and C{reverse} returning an L{RDNAP7Tuple} with C{NAPh} replaced 

396 by I{local} C{z}, the perpendicular distance to the local tangent plane (LTP). 

397 

398 This C{quasi-RD} transformer B{does not} implement any U{RD NAP<https://www.NSGI.NL/ 

399 coordinatenstelsels-en-transformaties/coordinatentransformaties/rdnap-etrs89-rdnaptrans>} 

400 specification and B{does not} provide I{Netherlands}' C{B{N}ormaal B{A}msterdams B{P}eil 

401 (NAP)} quasi-geodetic-height. 

402 ''' 

403 if _FOR_DOCS: 

404 __init__ = _LqRD.__init__ 

405 

406 def forward(self, lat_latlonh, lon=None, height=0, **name): # PYCHOK signature 

407 '''Convert I{geodetic} C{(lat, lon, height)} to I{local} C{quasi-RD (x, y, z)}. 

408 

409 @arg lat_latlonh: C{Scalar} (geodetic) latitude (C{degrees}) or a I{local} 

410 C{quasi-RD} L{RDNAP7Tuple}. 

411 @kwarg lon: C{Scalar} (geodetic) longitude (C{degrees}) iff B{C{lat_latlonh}} 

412 is C{scalar}, ignored otherwise. 

413 @kwarg height: Optional height (C{meter}, conventionally) perpendicular to and 

414 above (or below) the ellipsoid's surface, iff B{C{lat_latlonh}} 

415 is C{scalar}, ignored otherwise. 

416 @kwarg name: Optional C{B{name}=NN} (C{str}). 

417 

418 @return: An L{RDNAP7Tuple}C{(RDx, RDy, NAPh, lat, lon, height, datum)} with 

419 C{NAPh} set to I{local} C{z}. 

420 

421 @see: B{pygeodesy.LqRD.forward} for more information. 

422 ''' 

423 t = _LqRD.forward(self, lat_latlonh, lon=lon, height=height) 

424 return LqRD._l9t2r7t(t, **name) 

425 

426 def reverse(self, x_xyz, y=None, z=None, **name): # PYCHOK signature 

427 '''Convert I{local} C{quasi-RD (x, y, z)} to I{geodetic} C{(lat, lon, height)}. 

428 

429 @arg x_xyz: Local C{quasi-RD x} coordinate (C{scalar}) or a I{local} 

430 C{quasi-RD} L{RDNAP7Tuple}. 

431 @kwarg y: Local C{quasi-RD y} coordinate (C{meter}) iff B{C{x_xyz}} is 

432 C{scalar}, ignored otherwise. 

433 @kwarg z: Local C{z} coordinate (C{meter}) iff B{C{x_xyz}} is C{scalar}, 

434 ignored otherwise. 

435 @kwarg name: Optional C{B{name}=NN} (C{str}). 

436 

437 @return: An L{RDNAP7Tuple}C{(RDx, RDy, NAPh, lat, lon, height, datum)} 

438 with C{NAPh} set to I{local} B{C{z}}. 

439 

440 @see: B{pygeodesy.LqRD.reverse} for more information. 

441 ''' 

442 t = _LqRD.reverse(self, x_xyz, y=y, z=z) 

443 return LqRD._l9t2r7t(t, **name) 

444 

445 @staticmethod 

446 def _l9t2r7t(t, name=NN, **unused): # M=False 

447 return RDNAP7Tuple(t.x, t.y, t.z, # NAPh = t.z 

448 t.lat, t.lon, t.height, t.ecef.datum, name=name or t.name) 

449 

450 

451__all__ += _ALL_OTHER(LqRD, RDNAP7Tuple, Bounds4Tuple, # passed along from PyGeodesy 

452 Datums, LatLon2Tuple, PhiLam2Tuple, Similarity, Vector2Tuple, Vector3Tuple) 

453del _LQRD 

454 

455# **) MIT License 

456# 

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

458# 

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

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

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

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

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

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

465# 

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

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

468# 

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

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

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

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

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

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

475# OTHER DEALINGS IN THE SOFTWARE.