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
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-08 18:11 -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
35from math import fabs
36import os.path as os_path
37from time import time
39__all__ = ()
40__version__ = '26.05.08'
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
48def _line(ln):
49 return ' (line %s)' % (ln,)
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.
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.
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
73 _xinstanceof(str, bytes, self_txt=self_txt)
74 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R)
75 R_ = R.__class__.__name__
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
145def _zfmt(floats):
146 t = ('%s %.*f' % t for t in zip(_NAMES, _NDECS, floats))
147 return _COMMASPACE_.join(t)
150__all__ += _ALL_OTHER(validation3)
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.