Coverage for pyrdnap / rd0.py: 99%
209 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-31 14:31 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-31 14:31 -0400
2# -*- coding: utf-8 -*-
4u'''(INTERNAL) RijksDriehoeksmeting C{_RD} and reference C{_RD0}
5constants and classes C{RDNAP7Tuple} and C{LqRD}.
6'''
7# make sure int/int division yields float quotient in Py2
8from __future__ import division as _; del _ # noqa: E702 ;
10from pyrdnap.v_grids import _v_assert
11from pyrdnap.__pygeodesy import (_0_5, _1_0, _2_0, # PYCHOK used!
12 _isNAN, _isNAN0, _xinstanceof, _xsubclassof,
13 _LLEB, _xkwds,
14 _COMMASPACE_, _datum_, _lat_, _lon_, _height_, _N_,
15 _all_OTHER, _FOR_DOCS, _Pass, _NamedTuple)
16from pygeodesy import (map1, map2, NAN, NN, # basics, "consterns"
17 Datum, Datums, Similarity, # datums
18 Ellipsoid, Ellipsoids, LqRD as _LqRD, # ellipsoids, ltp
19 Bounds4Tuple, LatLon2Tuple, LatLon3Tuple, LatLon4Tuple, # namedTuples
20 PhiLam2Tuple, PhiLam3Tuple, PhiLam4Tuple, Vector2Tuple, Vector3Tuple,
21 deprecated_property_RO, Property_RO, property_ROver, pairs, # props, streprs
22 Height, Lam, Lamd, Lat, Lon, Meter, Phi, Phid, # units
23 sincos2, tanPI_2_2) # utily
25from math import atan2, ceil, fabs, floor, log, sin, sqrt
27__all__ = ()
28__version__ = '26.07.31'
30_LQRD0 = _LqRD() # get Amersfoort, region4, etc. (deleted below)
33def _c_f_N_f3(*deg_SW_D):
34 # return int(ceil) and int(floor) of Normalized
35 # and (Normalized less floor) of C{deg} degrees
36 N = _degN(*deg_SW_D)
37 # assert N >= 0, N
38 f = floor(N)
39 return int(ceil(N)), int(f), (N - f)
42def _degN(deg, degSW, deg_D):
43 # return C{deg} Normalized
44 return (deg - degSW) * deg_D
47class _RDbase(object):
48 '''(INTERNAL) Base.
49 '''
50 def _preDict(self, _pred, **d):
51 # return updated dict C{d}
52 for n in self.__class__.__dict__.keys():
53 if _pred(n):
54 d[n] = getattr(self, n)
55 return d
57 def toStr(self, prec=9, **fmt_ints):
58 # return this C{_RDx} as string
59 d = self._toDict() # PYCHOK OK
60 t = pairs(d, prec=prec, **fmt_ints)
61 return _COMMASPACE_(*t)
64class _RD(_RDbase):
65 '''(INTERNAL) Bounds, constants for RDNAP2018 (ASCII.txt).
66 '''
67 lat_D = Lat(lat_D=80.0) # degrees, all
68 lon_D = Lon(lon_D=50.0)
70# latD = Lat(latD=1 / lat_D) # degrees, all
71# lonD = Lon(lonD=1 / lon_D)
73 def __init__(self):
74 S, W, N, E = self._region4
75 nlat = _degN(N, S, self.lat_D) + _1_0 # 2.3.2g n-phi
76 nlon = _degN(E, W, self.lon_D) + _1_0 # 2.3.2g n-lambda
77 _v_assert(map1(int, nlat, nlon))
79 _bounds4 = _LQRD0.bounds4() # in .rdnap2018 Bounds4Tuple
81 def _c_f_N_f6(self, lat, lon):
82 # return (int(ceil), int(floor), Normalized less floor) of C{lat}) + \
83 # (int(ceil), int(floor), Normalized less floor) of C{lon})
84 S, W, _, _ = self._region4
85 return _c_f_N_f3(lat, S, self.lat_D) + \
86 _c_f_N_f3(lon, W, self.lon_D)
88 @property_ROver
89 def _RDNAPv0(self):
90 from pyrdnap.rdnap2018 import _RDNAPbase
91 return _RDNAPbase() # singleton, instance!
93 _region4 = _LQRD0.region4() # in .rdnap2018 Bounds4Tuple
95 def _toDict(self):
96 def _p(n): # lambda
97 return any(map(n.endswith, '4DS'))
99 return self._preDict(_p)
101 @property_ROver
102 def _xETRS2RD(self): # transform ETRS to RD-Bessel
103 return Similarity(tx=-565.7346, ty=-50.4058, tz=-465.2895, s=-4.07242,
104 rx=-1.91513, ry=1.60365, rz=-9.09546, name='_xETRS2RD')
106 @property_ROver
107 def _xRD2ETRS(self): # transform RD-Bessel to ETRS
108 return Similarity(tx=565.7381, ty=50.4018, tz=465.2904, s=4.07244,
109 rx=1.91514, ry=-1.60363, rz=9.09546, name='_xRD2ETRS')
111 # % python -c "import pyrdnap; print(pyrdnap.rd0._RD.toStr())"
112 # _bounds4=RD bounds (latS=50.75, lonW=2.539333, latN=55.765, lonE=7.22),
113 # _region4=RD region (latS=50.0, lonW=2.0, latN=56.0, lonE=8.0),
114 # _xETRS2RD=Similarity(name='_xETRS2RD', tx=-565.73, ty=-50.406, tz=-465.29, s=-4.0724,
115 # rx=-1.9151, ry=1.6037, rz=-9.0955),
116 # _xRD2ETRS=Similarity(name='_xRD2ETRS', tx=565.74, ty=50.402, tz=465.29, s=4.0724,
117 # rx=1.9151, ry=-1.6036, rz=9.0955),
118 # lat_D=80.0, lon_D=50.0 # latD=0.0125, lonD=0.02
120_RD = _RD() # PYCHOK singleton, in .test/testRndTrips
123class _RD0(_RDbase):
124 '''(INTERNAL) C{RD} Amersfoort, NL / C{RD New} constants for RDNAP2018 (ASCII.txt).
126 @see: U{EPSG:9809<https://EPSG.io/9809-method>}, U{"Oblique Stereographic"
127 <https://PROJ.org/en/stable/operations/projections/sterea.html>} and
128 <http://geotiff.maptools.org/proj_list/oblique_stereographic.html>
129 '''
130 H0 = Meter(H0 =_LQRD0.height0) # Amersfoort.height0 0.0 m
131 H0_ETRS = Meter(H0_ETRS=_LQRD0.height0_ETRS) # 43.0 m
132 K0 = 0.9999079 # 2.4.1 scale factor
133 LAT0 = Lat(LAT0=_LQRD0.Amersfoort.lat) # '52 9 22.178N' == 52.156160555555+°
134 LON0 = Lon(LON0=_LQRD0.Amersfoort.lon) # ' 5 23 15.5E' == 5.387638888888+°
135 LAM0 = Lamd(LAM0=LON0) # 𝜆0, 0.094032038
136 LAM0C = Lam(LAM0C=LAM0) # 𝛬0 on sphere == 𝜆0
137 PHI0 = Phid(PHI0=LAT0) # 𝜑0 0.910296727, PHI0C 𝛷0 set below
138 X0 = Meter(X0=155000.0) # false Easting 155029.784?
139 Y0 = Meter(Y0=463000.0) # false Norting 463109.889?
141# @property_ROver
142# def C0(self): # c, sphere
143# s, _ = self.sincos2PHI0
144# w = self._w1(s)
145# c = (w - _1_0) / (w + _1_0)
146# return (((self.N0 + s) * (_1_0 - c)) /
147# ((self.N0 - s) * (_1_0 + c)))
149# def chilam(self, lat, lon): # EPSG:9809
150# # return 2-tuple (chi, lam), conformal in radians
151# s, _ = sincos2d(lat)
152# w2 = self._w1(s) * self.C0
153# s = (w2 - _1_0) / (w2 + _1_0)
154# r = radians(lon - self.LON0) * self.N0
155# return asin(s), r
157 @property_ROver
158 def D0(self): # lazily
159 return Datums.Bessel1841
161 @property_ROver
162 def D80(self): # lazily
163 return Datums.GRS80
165 @property_ROver
166 def E0(self): # lazily
167 return self.D0.ellipsoid
169 def log_e_2(self, phi):
170 e = self.E0.e
171 p = e * sin(phi)
172 return log((_1_0 + p) / (_1_0 - p)) * (e * _0_5)
174 def log_tan(self, phi):
175 return log(tanPI_2_2(phi)) # tan((phi + PI/2) / 2)
177 @property_ROver
178 def M0(self): # 2.4.1 p 15 m
179 return self.W0 - self.N0 * self.Q0
181 @property_ROver
182 def N0(self): # 2.4.1 p 15 n, sphere
183 E = self.E0
184 _, c = self.sincos2PHI0
185 return sqrt(c**4 * E.e2 / E.e21 + _1_0)
187 @property_ROver
188 def PHI0C(self): # 2.4.1 p 15 𝛷0 on sphere
189 m, n = self.Rmn2
190 s, c = self.sincos2PHI0
191 return Phi(PHI0C=atan2(m * s, n * c)) # atan((m / n) * tan(PHI0))
193 @property_ROver
194 def Q0(self): # 2.4.1 p 15 q0
195 return self.log_tan(self.PHI0) - self.log_e_2(self.PHI0)
197 @property_ROver
198 def R(self): # 2.4.1 p 15 R, radius conformal sphere
199 m, n = self.Rmn2
200 return m * n
202 @property_ROver
203 def RK2(self): # 2.4.2
204 return self.R * self.K0 * _2_0
206 @property_ROver
207 def Rmn2(self): # 2.4.1 p 15 (sqrt(RsubM), sqrt(RsubN))
208 # RsubM, RsubN == RHO0, NU0 EPSG:9809
209 E = self.E0
210 s, _ = self.sincos2PHI0
211 s = _1_0 - s**2 * E.e2
212 # assert s > 0
213 N = E.a / sqrt(s)
214 # assert N > 0
215 M = E.e21 * N / s
216 # assert M > 0
217 return map1(sqrt, M, N) # sqrt!
219 @property_ROver
220 def sincos2PHI0(self): # 𝜑0
221 return sincos2(self.PHI0)
223 @property_ROver
224 def sincos2PHI0C(self): # 𝛷0
225 return sincos2(self.PHI0C)
227 def _toDict(self):
228 def _p(n): # lambda
229 return n.endswith('0') or n.startswith('R') or \
230 n.endswith('0C') # _0_
232 return self._preDict(_p, H0_ETRS=self.H0_ETRS)
234 @property_ROver
235 def W0(self): # 2.4.1 p 15 w0
236 return self.log_tan(self.PHI0C) # 𝛷0
238# def _w1(self, sphi): # EPSG:9809
239# w1 = NAN
240# if _1_0 > sphi > _N_1_0:
241# e = self.E0.e
242# S = (_1_0 + sphi) / (_1_0 - sphi)
243# T = (_1_0 - sphi * e) / (_1_0 + sphi * e)
244# w1 = pow(pow(T, e) * S, self.N0)
245# return w1
247 # % python -c "import pyrdnap; print(pyrdnap.rd0._RD0.toStr())"
248 # D0=Datum(name='Bessel1841', ellipsoid=Ellipsoids.Bessel1841, transform=Transforms.Bessel1841),
249 # D80=Datum(name='GRS80', ellipsoid=Ellipsoids.GRS80, transform=Transforms.WGS84),
250 # E0=Ellipsoid(name='Bessel1841', a=6377397.155, f=0.00334277, f_=299.1528128, b=6356078.962818),
251 # H0=0.0, H0_ETRS=43.0, K0=0.9999079, LAM0=0.094032038, LAM0C=0.094032038,
252 # LAT0=52.156160556, LON0=5.387638889, M0=0.003773954, N0=1.000475857,
253 # PHI0=0.910296727, PHI0C=0.909684757, Q0=1.06531844,
254 # R=6382644.571035411, RK2=12764113.458940838, Rmn2=(2524.794785679199, 2527.9854850929623),
255 # sincos2PHI0=(0.7896858198001045, 0.6135114554811807),
256 # sincos2PHI0C=(0.7893102212553742, 0.6139946047171686),
257 # W0=1.069599332, X0=155000.0, Y0=463000.0
259_RD0 = _RD0() # PYCHOK singleton, in .test/testRndTrips
262class LatLonN3Tuple(_NamedTuple): # XXX move to pygeodesy
263 '''3-tuple C{(lat, lon, N)} with geoid height C{N} in C{meter}, conventionally.
264 '''
265 _Names_ = (_lat_, _lon_, _N_)
266 _Units_ = ( Lat, Lon, Height)
269class RDNAP7Tuple(_NamedTuple): # in .v_self
270 '''7-Tuple C{(RDx, RDy, H, lat, lon, height, datum)} with I{local} C{RDx}, C{RDy}
271 and (orthometric) height C{H}, geodetic C{lat}, C{lon}, (ellipsoidal) C{height}
272 and C{datum} with C{lat} and C{lon} in C{degrees} and with C{RDx}, C{RDy}, C{H}
273 and C{height} in C{meter}, conventionally.
275 @note: I{By default} C{lat}, C{lon} and C{datum} are B{GRS80 (ETRS89)} when
276 returned from L{RDNAP2018v1.reverse} but B{Bessel1841 (RD-Bessel)}
277 from L{RDNAP2018v2.reverse}.
278 '''
279 _Names_ = ('RDx', 'RDy', 'H', _lat_, _lon_, _height_, _datum_)
280 _Units_ = ( Meter, Meter, Meter, Lat, Lon, Height, _Pass)
282 def diff(self, other, datum=None, **name):
283 '''Return the difference between this and an C{other} C{RDNAP7Tuple}.
285 @kwarg datum: Datum C{diff} (C{Datum}, None or NAN).
286 @kwarg name: Optional name (C{str}).
288 @return: An L{RDNAP7Tuple} with the C{fabs(diff)} for each item,
289 except C{datum} as B{C{datum}}.
290 '''
291 def _diff(a, b):
292 try:
293 return fabs(a - b)
294 except TypeError:
295 return datum
297 _xinstanceof(RDNAP7Tuple, other=other)
298 t = map2(_diff, self, other)
299 return RDNAP7Tuple(t, **name)
301 @Property_RO
302 def lam(self):
303 '''Get the longitude (B{C{radians}}).
304 '''
305 return Lamd(self.lon) # PYCHOK lon
307 @Property_RO
308 def latlon(self):
309 '''Get the lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).
310 '''
311 return LatLon2Tuple(self.lat, self.lon, name=self.name)
313 @Property_RO
314 def latlonheight(self):
315 '''Get the lat-, longitude in C{degrees} and height (L{LatLon3Tuple}C{(lat, lon, height)}).
316 '''
317 return self.latlon.to3Tuple(self.height)
319 @Property_RO
320 def latlonheightdatum(self):
321 '''Get the lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).
322 '''
323 return self.latlonheight.to4Tuple(self.datum)
325 @Property_RO
326 def latlonNgeoid(self):
327 '''Get the lat-, longitude in C{degrees} and geoid height (L{LatLonN3Tuple}C{(lat, lon, N)}).
328 '''
329 return LatLonN3Tuple(self.lat, self.lon, self.N, name=self.name)
331 @Property_RO
332 def N(self):
333 '''Get the geoid height C{N} (C{meter}, conventionally).
334 '''
335 N = self.height - self.H
336 return NAN if _isNAN(N) else Height(N=N)
338 @deprecated_property_RO
339 def NAPh(self):
340 '''DEPRECATED on 2026.07.21, use attribute C{H}.'''
341 return self.H # PYCHOK H
343 @Property_RO
344 def phi(self):
345 '''Get the latitude (B{C{radians}}).
346 '''
347 return Phid(self.lat) # PYCHOK lat
349 @Property_RO
350 def philam(self):
351 '''Get the lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).
352 '''
353 return PhiLam2Tuple(self.phi, self.lam, name=self.name) # PYCHOK lam, phi
355 @Property_RO
356 def philamheight(self):
357 '''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
358 '''
359 return self.philam.to3Tuple(self.height) # PYCHOK height
361 @Property_RO
362 def philamheightdatum(self):
363 '''Get the lat-, longitude in C{radians} with height and datum (L{PhiLamn4Tuple}C{(phi, lam, height, datum)}).
364 '''
365 return self.philamheight.to4Tuple(self.datum)
367 def toDatum(self, datum2, name=NN):
368 '''Convert this C{lat}, C{lon} and C{height} to B{C{datum2}}.
370 @arg datum2: Datum to convert I{to} (L{Datum}).
371 @kwarg name: Optional name (C{str}), overriding this name.
373 @return: An L{RDNAP7Tuple} with transformed C{lat}, C{lon} and C{height}
374 or this L{RDNAP7Tuple} if this.datum is B{C{datum2}}.
376 @note: This datum conversion is based on C{pygeodesy} which differs from
377 C{RDNAPTRANS(tm)2018_v220627}.
379 @see: Methods L{RDNAP7Tuple.toETRS} and L{RDNAP7Tuple.toRD}.
380 '''
381 _xinstanceof(Datum, datum2=datum2)
382 if self.datum is datum2 or self.datum == datum2: # PYCHOK datum
383 return self
384 g = self.toLatLon(_LLEB).toDatum(datum2)
385 h = NAN if _isNAN(self.height) else g.height # PYCHOK preserve height NAN
386 return self.dup(lat=g.lat, lon=g.lon, datum=g.datum, height=h,
387 name=name or self.name)
389 def toETRS(self, **name):
390 '''Copy this L{RDNAP7Tuple} with C{lat} and C{lon} C{reverse3} transformed
391 to ETRS89 (GRS80), provided this C{datum} is RD-Bessel (Bessel1841).
393 @kwarg name: Optional name (C{str}), overriding this name.
395 @see: Methods L{RDNAP7Tuple.toRD} and L{RDNAP7Tuple.toDatum}.
396 '''
397 return self._toX(_RD0.D0, _RD._RDNAPv0.reverse3, **name)
399 def toLatLon(self, LatLon, **LatLon_kwds):
400 '''Return this C{lat}, C{lon}, C{datum} and C{height} as B{C{LatLon}}.
402 @arg LatLon: An ellipsoidal C{LatLon} class (C{pygeodesy.ellipsoidal*}).
403 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments.
405 @return: An B{C{LatLon}} instance.
407 @raise TypeError: B{C{LatLon}} not ellipsoidal or an other issue.
408 '''
409 _xsubclassof(_LLEB, LatLon=LatLon)
410 h = _isNAN0(self.height) # PYCHOK height
411 kwds = _xkwds(LatLon_kwds, name=self.name, height=h)
412 return LatLon(self.lat, self.lon, datum=self.datum, **kwds) # PYCHOK datum
414 def toRD(self, **name):
415 '''Copy this L{RDNAP7Tuple} with C{lat} and C{lon} C{forward3} transformed
416 to RD-Bessel (Bessel1841), provided this C{datum} is ETRS89 (GRS80).
418 @kwarg name: Optional name (C{str}), overriding this name.
420 @see: Methods L{RDNAP7Tuple.toETRS} and L{RDNAP7Tuple.toDatum}.
421 '''
422 return self._toX(_RD0.D80, _RD._RDNAPv0.forward3, **name)
424 def _toX(self, datum, _xform, name=NN):
425 # helper for C{toETRS} and C{toRD}
426 if self.datum is datum or self.datum == datum: # PYCHOK datum
427 lat, lon, d = _xform(*self.latlon)
428 return self.dup(lat=lat, lon=lon, datum=d, name=name or self.name)
429 return self
431 @Property_RO
432 def xy(self):
433 '''Get the I{local} RDx, RDy coordinates (L{Vector2Tuple}C{(x, y)}).
434 '''
435 return Vector2Tuple(self.RDx, self.RDy, name=self.name)
437 @Property_RO
438 def xyz(self):
439 '''Get the I{local} RDx, RDy coordinates and (orthometric) height (L{Vector3Tuple}C{(x, y, z)}).
440 '''
441 return Vector3Tuple(self.RDx, self.RDy, self.H, name=self.name)
444class LqRD(_LqRD):
445 '''Like U{pygeodesy.LqRD<https://mrJean1.GitHub.io/PyGeodesy/docs/pygeodesy.ltp.LqRD-class.html>}
446 but with methods C{forward} and C{reverse} returning an L{RDNAP7Tuple} with C{H} replaced
447 by I{local} C{z}, the perpendicular distance to the local tangent plane (LTP).
449 This C{quasi-RD} transformer B{does not} implement any U{RD NAP<https://www.NSGI.NL/
450 coordinatenstelsels-en-transformaties/coordinatentransformaties/rdnap-etrs89-rdnaptrans>}
451 specification and B{does not} provide I{Netherlands}' C{B{N}ormaal B{A}msterdams B{P}eil
452 (NAP)} quasi-geodetic-height.
453 '''
454 if _FOR_DOCS:
455 __init__ = _LqRD.__init__
457 def forward(self, lat_latlonh, lon=None, height=0, **name): # PYCHOK signature
458 '''Convert I{geodetic} C{(lat, lon, height)} to I{local} C{quasi-RD (x, y, z)}.
460 @arg lat_latlonh: C{Scalar} (geodetic) latitude (C{degrees}) or a I{local}
461 C{quasi-RD} L{RDNAP7Tuple}.
462 @kwarg lon: C{Scalar} (geodetic) longitude (C{degrees}) iff B{C{lat_latlonh}}
463 is C{scalar}, ignored otherwise.
464 @kwarg height: Optional height (C{meter}, conventionally) perpendicular to and
465 above (or below) the ellipsoid's surface, iff B{C{lat_latlonh}}
466 is C{scalar}, ignored otherwise.
467 @kwarg name: Optional C{B{name}=NN} (C{str}).
469 @return: An L{RDNAP7Tuple}C{(RDx, RDy, H, lat, lon, height, datum)} with C{H}
470 set to I{local} C{z}.
472 @see: C{pygeodesy.LqRD.forward} for more information.
473 '''
474 t = _LqRD.forward(self, lat_latlonh, lon=lon, height=height)
475 return LqRD._l9t2r7t(t, **name)
477 def reverse(self, x_xyz, y=None, z=None, **name): # PYCHOK signature
478 '''Convert I{local} C{quasi-RD (x, y, z)} to I{geodetic} C{(lat, lon, height)}.
480 @arg x_xyz: Local C{quasi-RD x} coordinate (C{scalar}) or a I{local}
481 C{quasi-RD} L{RDNAP7Tuple}.
482 @kwarg y: Local C{quasi-RD y} coordinate (C{meter}) iff B{C{x_xyz}} is
483 C{scalar}, ignored otherwise.
484 @kwarg z: Local C{z} coordinate (C{meter}) iff B{C{x_xyz}} is C{scalar},
485 ignored otherwise.
486 @kwarg name: Optional C{B{name}=NN} (C{str}).
488 @return: An L{RDNAP7Tuple}C{(RDx, RDy, H, lat, lon, height, datum)} with C{H}
489 set to I{local} B{C{z}}.
491 @see: C{pygeodesy.LqRD.reverse} for more information.
492 '''
493 t = _LqRD.reverse(self, x_xyz, y=y, z=z)
494 return LqRD._l9t2r7t(t, **name)
496 @staticmethod
497 def _l9t2r7t(t, name=NN, **unused): # M=False
498 return RDNAP7Tuple(t.x, t.y, t.z, # H = t.z
499 t.lat, t.lon, t.height, t.ecef.datum, name=name or t.name)
502__all__ += _all_OTHER(LqRD, LatLonN3Tuple, RDNAP7Tuple, # passed along from PyGeodesy
503 Bounds4Tuple, Datum, Datums, Ellipsoid, Ellipsoids,
504 LatLon2Tuple, LatLon3Tuple, LatLon4Tuple,
505 PhiLam2Tuple, PhiLam3Tuple, PhiLam4Tuple,
506 Similarity, Vector2Tuple, Vector3Tuple)
507del _all_OTHER, _LQRD0
509# **) MIT License
510#
511# Copyright (C) 2026-2026 -- mrJean1 at Gmail -- All Rights Reserved.
512#
513# Permission is hereby granted, free of charge, to any person obtaining a
514# copy of this software and associated documentation files (the "Software"),
515# to deal in the Software without restriction, including without limitation
516# the rights to use, copy, modify, merge, publish, distribute, sublicense,
517# and/or sell copies of the Software, and to permit persons to whom the
518# Software is furnished to do so, subject to the following conditions:
519#
520# The above copyright notice and this permission notice shall be included
521# in all copies or substantial portions of the Software.
522#
523# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
524# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
525# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
526# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
527# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
528# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
529# OTHER DEALINGS IN THE SOFTWARE.