Coverage for pyrdnap/v_self.py: 88%

81 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-05-08 18:11 -0400

1 

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

3 

4u'''Function L{validation3} to run the C{RD NAP 2018} self-validation tests 

5C{.../RDNAPTRANS2018_v220627/.../Z001_ETRS89andRDNAP.txt} obtainable from U{NSGI.NL 

6<https://www.NSGI.NL/coordinatenstelsels-en-transformaties/coordinatentransformaties/rdnap-etrs89-rdnaptrans>} 

7after registration. 

8 

9For each test line, 3 lines are produced: the 1st showing the point C{id}, the original 

10(ETRS89) C{lat}, C{lon} and C{height} and the expected C{RDx}, C{RDy} and C{NAPh} values. 

11 

12The 2nd line shows the C{lat}, C{lon} and C{height} and C{RDx}, C{RDy} and {NAPh} results 

13from the L{RDNAP2018v1} or C{-v2} transformer's L{reverse<pyrdnap.RDNAP2018v1.reverse>} 

14respectively L{forward<pyrdnap.RDNAP2018v1.forward>} method. 

15 

16The 3rd line contains the (absolute value) of the differences between the results on 

17the 2nd line and the corresponding, original value on the 1st line. 

18 

19The final 2 lines of the output are the C{maximum} of all (absolute value) differences 

20followed by a line with the formal C{RDNAP 2018} requirements, C{0.000000010 degrees} 

21or C{0.0010 meter} for each result. 

22 

23@note: A test C{FAILED} if any C{reverse} or C{forward} result I{exceeds} the C{RD NAP 

24 2018} requirement for that result. 

25 

26@note: For tests with C{NAPh} marked C{"*"}, only the C{reverse} C{lat} and C{lon} 

27 and C{forward} C{RDx} and C{RDy} results are taken into account. 

28 

29@see: Module L{pyrdnap<pyrdnap.__main__>} for examples to invoke L{validation3}. 

30''' 

31from pyrdnap.rd0 import RDNAP7Tuple 

32from pyrdnap.__pygeodesy import _ALL_OTHER, _COMMASPACE_, _NL_, _secs2str, _xinstanceof 

33from pygeodesy import NN, NAN 

34 

35from math import fabs 

36import os.path as os_path 

37from time import time 

38 

39__all__ = () 

40__version__ = '26.05.08' 

41 

42_NAMES = RDNAP7Tuple._Names_[3:6] + RDNAP7Tuple._Names_[:3] 

43# (lat lon height RDx RDy NAPh) 

44_REQ_D = (1e-8, 1e-8, 1e-3, 1e-3, 1e-3, 1e-3) # 0 == ignore 

45_NDECS = (11, 11, 6, 8, 8, 8) # fmt precision 

46 

47 

48def _line(ln): 

49 return ' (line %s)' % (ln,) 

50 

51 

52def validation3(self_txt, R, all_=False, in_out=True, _print=None, _printest=None): # MCCABE 13 

53 '''Run the official C{RD NAP 2018} self-validation tests. 

54 

55 @arg self_txt: Name of the file containing the C{RDNAP 2018} self-validation tests 

56 (C{str}), C{.../RDNAPTRANS2018_v220627/.../Z001_ETRS89andRDNAP.txt}. 

57 @arg R: An RDNAP2018v# transformer (L{RDNAP2018v1} or L{RDNAP2018v2} instance). 

58 @kwarg all_: If C{True} print all tests and test results, otherwise only failing 

59 tests (C{bool}). 

60 @kwarg in_out: If C{True} test only points C{inside} the C{RD} region, if C{False} 

61 only points C{outside} (C{bool}). 

62 @kwarg _print: A Python 3+ C{print}-like callable or C{None} to not print the header 

63 and the final, summary lines. 

64 @kwarg _printest: A Python 3+ C{print}-like callable or C{None} to not print B{C{all_}} 

65 B{C{in_out}} tests or only the failing ones. 

66 

67 @return: 3-Tuple C{(failed, total, in_outside)} with the number of C{FAILED} tests, 

68 the C{total} number of tests and the number of test lines B{C{in_out}} the 

69 C{RD} region. 

70 ''' 

71 from pyrdnap import RDNAP2018v1, RDNAP2018v2, RDNAPError, _versions 

72 

73 _xinstanceof(str, bytes, self_txt=self_txt) 

74 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R) 

75 R_ = R.__class__.__name__ 

76 

77 nfail = ntotal = nin_out = 0 

78 if _print: 

79 _print('testing', repr(R)) 

80 _print(' using', repr(self_txt)) 

81 if self_txt and os_path.exists(self_txt): 

82 diffs = [0] * len(_REQ_D) # max |diff| of all 

83 with open(self_txt, 'rb') as f: 

84 hd = f.readline().strip().decode('utf-8') 

85 ln = 1 

86 if _print: 

87 _print(' header', repr(hd), _line(ln), _NL_) 

88 ds = list(diffs) # |diff| per line 

89 t0 = time() 

90 while True: 

91 bs = f.readline().strip().split() 

92 if not bs: 

93 break 

94 ln += 1 

95 if bs[6] == b'*': # xpec_d and res, each a 5-tuple of floats 

96 lat, lon, h, RDx, RDy = xpec_d = tuple(map(float, bs[1:-1])) 

97 res = R.reverse(RDx, RDy).latlon + (NAN,) + R.forward(lat, lon, h).xy 

98 ds[5] = NAN 

99 else: # xpec and res, each a 6-tuple of floats 

100 lat, lon, h, RDx, RDy, NAPh = xpec_d = tuple(map(float, bs[1:])) 

101 res = R.reverse(RDx, RDy, NAPh).latlonheight + R.forward(lat, lon, h).xyz 

102 # assert len(res) == len(xpec) 

103 if in_out == bool(R.isinside(lat, lon)): 

104 nin_out += 1 

105 F = NN # PASSED 

106 for i, (m, q, r, x) in enumerate(zip(diffs, _REQ_D, res, xpec_d)): 

107 if q > 0: 

108 ds[i] = d = fabs(r - x) 

109 if d > m: # new max |diff| 

110 diffs[i] = d 

111 if d > q: 

112 nfail += 1 

113 F = 'FAILED' 

114 ntotal += 1 

115 else: 

116 ds[i] = NAN 

117 if _printest and (F or all_): 

118 b = b' '.join(bs).decode('utf-8') 

119 _printest('id', b, _line(ln)) 

120 _printest(R_, _zfmt(res), F) 

121 _printest(' |diff|', _zfmt(ds), F, _NL_) 

122 if _print: 

123 s = time() - t0 

124 t = '-inside' if in_out else '-outside' 

125 if _printest: 

126 t += ' -all' if all_ else ' -failed' 

127 t = '%s of %s lines %s' % (nin_out, (ln - 1), t) 

128 t = '%s (%s) %s' % (t, _versions(), _secs2str(s)) 

129 if nfail: 

130 _print(R_, nfail, 'of', ntotal, 'tests', 'FAILED,', t) 

131 else: 

132 _print(R_, 'all', ntotal, 'tests', 'PASSED,', t) 

133 for n, fs in (('req', _REQ_D), ('max', diffs)): 

134 _print(R_, n, '|diff|', _zfmt(fs)) 

135 else: 

136 t = "file %r doesn't exist" % (self_txt,) 

137 if _print: 

138 _print(t) 

139 else: 

140 raise RDNAPError(t) 

141 nfail = 1 

142 return nfail, ntotal, nin_out 

143 

144 

145def _zfmt(floats): 

146 t = ('%s %.*f' % t for t in zip(_NAMES, _NDECS, floats)) 

147 return _COMMASPACE_.join(t) 

148 

149 

150__all__ += _ALL_OTHER(validation3) 

151 

152# **) MIT License 

153# 

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

155# 

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

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

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

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

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

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

162# 

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

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

165# 

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

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

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

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

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

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

172# OTHER DEALINGS IN THE SOFTWARE.