Coverage for pyrdnap / v_self.py: 95%
82 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-06-10 10:52 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-06-10 10:52 -0400
2# -*- coding: utf-8 -*-
4u'''Function L{validation3} to run the C{RD NAP 2018} self-validation test set
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 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.
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 for each result on the 2nd
17line and the corresponding, original test point value on the 1st line.
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.
23A test C{FAILED} if any C{reverse} or C{forward} result I{exceeds} the C{RD NAP 2018}
24requirement for that result.
26For points with C{NAPh} marked C{"*"}, C{NAPh} is set to C{NAN}.
28@see: Module L{pyrdnap<pyrdnap.__main__>} for examples to invoke L{validation3}.
29'''
30from pyrdnap.rd0 import RDNAP7Tuple
31from pyrdnap.__pygeodesy import (_ALL_OTHER, _COMMASPACE_, _NAN_, _NL_,
32 _SPACE_, _STAR_, _secs2str, _xinstanceof)
33from pygeodesy import NAN, NN, map2, typename
35from math import fabs
36import os.path as os_path
37from time import time
39__all__ = ()
40__version__ = '26.06.09'
42_NAMES = RDNAP7Tuple._Names_[3:6] + RDNAP7Tuple._Names_[0: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): # in .__main__
50 return ' (line %s)' % (ln,)
53def _readlines(filename): # in .__main__
54 # yield each line as str, not bytes
55 with open(filename, 'rb') as f:
56 t = f.readline()
57 while t:
58 yield t.strip().decode('utf-8')
59 t = f.readline()
62def validation3(self_txt, R, all_=False, asRD=False, in_out=True, _print=None, _printest=None): # MCCABE 18
63 '''Run the C{RD NAP 2018} self-validation test set.
65 @arg self_txt: Name of the file containing the C{RD NAP 2018} self-validation tests
66 (C{str}), C{.../RDNAPTRANS2018_v220627/.../Z001_ETRS89andRDNAP.txt}.
67 @arg R: An RDNAP2018v# transformer (L{RDNAP2018v1} or L{RDNAP2018v2} instance).
68 @kwarg all_: If C{True} print all tests and test results, otherwise only failing
69 tests (C{bool}).
70 @kwarg asRD: If C{True} use RD_Bessel lat- and longitudes and datum from C{reverse}
71 results (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.
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
85 _xinstanceof(str, bytes, self_txt=self_txt)
86 _xinstanceof(RDNAP2018v1, RDNAP2018v2, R=R)
87 R_ = typename(R)
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() # list
101 if ts[6] == _STAR_:
102 ts[6] = _NAN_
103 lat, lon, h, RDx, RDy, NAPh = xs = map2(float, ts[1:])
104 if in_out == bool(R.isinside(lat, lon)):
105 F = NN # PASSED
106 rs = R.reverse(RDx, RDy, NAPh, asRD=asRD).latlonheight + \
107 R.forward(lat, lon, h).xyz
108 ntotal += len(rs)
109 nin_out += 1
110 for i, (m, q, r, x) in enumerate(zip(diffs, _REQ_D, rs, xs)):
111 if q > 0:
112 ds[i] = d = fabs(r - x)
113 if d > m: # new max |diff|
114 diffs[i] = d
115 if d > q:
116 nfailed += 1
117 F = 'FAILED'
118 else: # PYCHOK no cover
119 ntotal -= 1
120 ds[i] = NAN
121 if _printest and (F or all_):
122 _printest('id', _SPACE_(*ts), _line(ln))
123 _printest(R_, _zfmt(rs), F)
124 _printest(' |diff|', _zfe4(ds), F, _NL_)
125 else: # 1st line
126 if _print:
127 _print(' header', repr(t), _line(ln), _NL_)
128 t0 = time()
130 if _print:
131 s = time() - t0
132 t = '-inside' if in_out else '-outside'
133 if _printest:
134 t += ' -all' if all_ else ' -failed'
135 t = '%s of %s points %s' % (nin_out, (ln - 1), t)
136 t = '%s (%s) %s' % (t, _versions(), _secs2str(s))
137 if asRD:
138 t += ' -asRD'
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: # PYCHOK no cover
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
156def _zfe4(floats):
157 t = ('%s %.*e' % t for t in zip(_NAMES, _NDecs, floats))
158 return _COMMASPACE_.join(t)
161def _zfmt(floats):
162 t = ('%s %.*f' % t for t in zip(_NAMES, _NDECS, floats))
163 return _COMMASPACE_.join(t)
166__all__ += _ALL_OTHER(validation3)
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.