Coverage for pyrdnap / v_self.py: 90%

88 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-27 11:33 -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{RD NAP 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_, _SPACE_, _STAR_, 

33 _secs2str, _xinstanceof) 

34from pygeodesy import NN, NAN, map2, typename 

35 

36from math import fabs 

37import os.path as os_path 

38from time import time 

39 

40__all__ = () 

41__version__ = '26.05.27' 

42 

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

44# (lat lon height RDx RDy NAPh) 

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

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

47_NDecs = tuple((_ - 4) for _ in _NDECS) # fe4 precision 

48 

49 

50def _line(ln): # in .__main__ 

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

52 

53 

54def _readlines(filename): # PYCHOK in .__main__, .v_self, setup.py 

55 # yield each line as str, not bytes 

56 with open(filename, 'rb') as f: 

57 while True: 

58 t = f.readline() 

59 if not t: 

60 break 

61 yield t.strip().decode('utf-8') 

62 

63 

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

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

66 

67 @arg self_txt: Name of the file containing the C{RD NAP 2018} self-validation tests 

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

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

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

71 tests (C{bool}). 

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

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

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

75 and the final, summary lines. 

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

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

78 

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

80 the C{total} number of tests and the number of points B{C{in_out}} the C{RD} 

81 region. 

82 ''' 

83 from pyrdnap import RDNAP2018v1, RDNAP2018v2, RDNAPError, _versions 

84 

85 _xinstanceof(str, bytes, self_txt=self_txt) 

86 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R) 

87 R_ = typename(R) 

88 

89 nfailed = ntotal = nin_out = 0 

90 if _print: 

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

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

93 if self_txt and os_path.exists(self_txt): 

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

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

96 ln = t0 = 0 

97 for t in _readlines(self_txt): 

98 ln += 1 

99 if t0: 

100 ts = t.split() 

101 if ts[6] == _STAR_: # xpec_d and res, each a 5-tuple of floats 

102 lat, lon, h, RDx, RDy = xpec_d = map2(float, ts[1:-1]) 

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

104 ds[5] = NAN 

105 else: # xpec_d and res, each a 6-tuple of floats 

106 lat, lon, h, RDx, RDy, NAPh = xpec_d = map2(float, ts[1:]) 

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

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

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

110 nin_out += 1 

111 F = NN # PASSED 

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

113 if q > 0: 

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

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

116 diffs[i] = d 

117 if d > q: 

118 nfailed += 1 

119 F = 'FAILED' 

120 ntotal += 1 

121 else: 

122 ds[i] = NAN 

123 if _printest and (F or all_): 

124 _printest('id', _SPACE_(*ts), _line(ln)) 

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

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

127 else: # 1st line 

128 if _print: 

129 _print(' header', repr(t), _line(ln), _NL_) 

130 t0 = time() 

131 

132 if _print: 

133 s = time() - t0 

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

135 if _printest: 

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

137 t = '%s of %s points %s' % (nin_out, (ln - 1), t) 

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

139 if nfailed: 

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

141 else: 

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

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

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

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

146 else: 

147 t = "doesn't exist: %r" % (self_txt,) 

148 if _print: 

149 _print(t) 

150 else: 

151 raise RDNAPError(t) 

152 nfailed = 1 

153 return nfailed, ntotal, nin_out 

154 

155 

156def _zfe4(floats): 

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

158 return _COMMASPACE_.join(t) 

159 

160 

161def _zfmt(floats): 

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

163 return _COMMASPACE_.join(t) 

164 

165 

166__all__ += _ALL_OTHER(validation3) 

167 

168# **) MIT License 

169# 

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

171# 

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

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

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

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

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

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

178# 

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

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

181# 

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

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

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

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

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

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

188# OTHER DEALINGS IN THE SOFTWARE.