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
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-09 18:20 -0400
2# -*- coding: utf-8 -*-
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.
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.
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.
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.
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.
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.
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.
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
35from math import fabs
36import os.path as os_path
37from time import time
39__all__ = ()
40__version__ = '26.05.09'
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
49def _line(ln):
50 return ' (line %s)' % (ln,)
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.
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.
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
74 _xinstanceof(str, bytes, self_txt=self_txt)
75 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R)
76 R_ = typename(R)
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
147def _zfe4(floats):
148 t = ('%s %.*e' % t for t in zip(_NAMES, _NDecs, floats))
149 return _COMMASPACE_.join(t)
152def _zfmt(floats):
153 t = ('%s %.*f' % t for t in zip(_NAMES, _NDECS, floats))
154 return _COMMASPACE_.join(t)
157__all__ += _ALL_OTHER(validation3)
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.