Coverage for pyrdnap/v_self.py: 88%

85 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-05-14 16:01 -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 point, 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 lines of the output are the C{maximum} of all (absolute value) differences in 

202 formats and a line with the C{RDNAP 2018} requirements, C{0.000000010 degrees} or 

21C{0.0010 meter} for each result. 

22 

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

24requirement for that result. 

25 

26For points with C{NAPh} marked C{"*"}, only the C{reverse} C{lat} and C{lon} and 

27C{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.13' 

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): # in .__main__ 

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 points B{C{in_out}} the C{RD} 

70 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 points %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 = "doesn't exist: %r" % (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.