Coverage for pyrdnap/v_self.py: 88%

85 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-05-09 18:20 -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, typename 

34 

35from math import fabs 

36import os.path as os_path 

37from time import time 

38 

39__all__ = () 

40__version__ = '26.05.09' 

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_NDecs = tuple((_ - 4) for _ in _NDECS) # fe4 precision 

47 

48 

49def _line(ln): 

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

51 

52 

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

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

55 

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

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

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

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

60 tests (C{bool}). 

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

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

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

64 and the final, summary lines. 

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

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

67 

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

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

70 C{RD} region. 

71 ''' 

72 from pyrdnap import RDNAP2018v1, RDNAP2018v2, RDNAPError, _versions 

73 

74 _xinstanceof(str, bytes, self_txt=self_txt) 

75 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R) 

76 R_ = typename(R) 

77 

78 nfailed = ntotal = nin_out = 0 

79 if _print: 

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

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

82 if self_txt and os_path.exists(self_txt): 

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

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

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

86 ln = 1 

87 if _print: 

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

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

90 t0 = time() 

91 while True: 

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

93 if not bs: 

94 break 

95 ln += 1 

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

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

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

99 ds[5] = NAN 

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

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

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

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

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

105 nin_out += 1 

106 F = NN # PASSED 

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

108 if q > 0: 

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

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

111 diffs[i] = d 

112 if d > q: 

113 nfailed += 1 

114 F = 'FAILED' 

115 ntotal += 1 

116 else: 

117 ds[i] = NAN 

118 if _printest and (F or all_): 

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

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

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

122 _printest(' |diff|', _zfe4(ds), F, _NL_) 

123 if _print: 

124 s = time() - t0 

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

126 if _printest: 

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

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

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

130 if nfailed: 

131 _print(R_, nfailed, 'of', ntotal, 'tests', 'FAILED,', t) 

132 else: 

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

134 for n, _z, fs in (('req', _zfmt, _REQ_D), ('max', _zfe4, diffs), 

135 ('max', _zfmt, diffs)): 

136 _print(R_, n, '|diff|', _z(fs)) 

137 else: 

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

139 if _print: 

140 _print(t) 

141 else: 

142 raise RDNAPError(t) 

143 nfailed = 1 

144 return nfailed, ntotal, nin_out 

145 

146 

147def _zfe4(floats): 

148 t = ('%s %.*e' % t for t in zip(_NAMES, _NDecs, floats)) 

149 return _COMMASPACE_.join(t) 

150 

151 

152def _zfmt(floats): 

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

154 return _COMMASPACE_.join(t) 

155 

156 

157__all__ += _ALL_OTHER(validation3) 

158 

159# **) MIT License 

160# 

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

162# 

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

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

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

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

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

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

169# 

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

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

172# 

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

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

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

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

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

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

179# OTHER DEALINGS IN THE SOFTWARE.