pygeodesy 24.3.24__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. pygeodesy/wgrs.py +439 -0
@@ -0,0 +1,383 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Web Mercator (WM) projection.
5
+
6
+ Classes L{Wm} and L{WebMercatorError} and functions L{parseWM} and L{toWm}.
7
+
8
+ Pure Python implementation of a U{Web Mercator<https://WikiPedia.org/wiki/Web_Mercator>}
9
+ (aka I{Pseudo-Mercator}) class and conversion functions for spherical and near-spherical
10
+ earth models.
11
+
12
+ @see: U{Google Maps / Bing Maps Spherical Mercator Projection
13
+ <https://AlastairA.WordPress.com/2011/01/23/the-google-maps-bing-maps-spherical-mercator-projection>},
14
+ U{Geomatics Guidance Note 7, part 2<https://www.IOGP.org/wp-content/uploads/2019/09/373-07-02.pdf>}
15
+ and U{Implementation Practice Web Mercator Map Projection<https://Web.Archive.org/web/20141009142830/
16
+ http://earth-info.nga.mil/GandG/wgs84/web_mercator/(U)%20NGA_SIG_0011_1.0.0_WEBMERC.pdf>}.
17
+ '''
18
+ # make sure int/int division yields float quotient, see .basics
19
+ from __future__ import division as _; del _ # PYCHOK semicolon
20
+
21
+ from pygeodesy.basics import _splituple, _xinstanceof
22
+ from pygeodesy.constants import PI_2, R_MA, _2_0
23
+ from pygeodesy.datums import Datum, _spherical_datum
24
+ from pygeodesy.dms import clipDegrees, parseDMS2
25
+ from pygeodesy.errors import _parseX, _ValueError, _xattr, _xkwds
26
+ from pygeodesy.interns import NN, _COMMASPACE_, _datum_, _earth_, _easting_, \
27
+ _northing_, _radius_, _SPACE_, _x_, _y_
28
+ # from pygeodesy.lazily import _ALL_LAZY from .named
29
+ from pygeodesy.named import _NamedBase, _NamedTuple, _ALL_LAZY
30
+ from pygeodesy.namedTuples import LatLon2Tuple, LatLonDatum3Tuple, PhiLam2Tuple
31
+ from pygeodesy.props import deprecated_method, Property_RO
32
+ from pygeodesy.streprs import Fmt, strs, _xzipairs
33
+ from pygeodesy.units import Easting, _isRadius, Lat, Northing, Radius
34
+ from pygeodesy.utily import degrees90, degrees180
35
+
36
+ from math import atan, atanh, exp, radians, sin, tanh
37
+
38
+ __all__ = _ALL_LAZY.webmercator
39
+ __version__ = '24.02.04'
40
+
41
+ # _FalseEasting = 0 # false Easting (C{meter})
42
+ # _FalseNorthing = 0 # false Northing (C{meter})
43
+ _LatLimit = Lat(limit=85.051129) # latitudinal limit (C{degrees})
44
+ # _LonOrigin = 0 # longitude of natural origin (C{degrees})
45
+
46
+
47
+ class EasNorRadius3Tuple(_NamedTuple):
48
+ '''3-Tuple C{(easting, northing, radius)}, all in C{meter}.
49
+ '''
50
+ _Names_ = (_easting_, _northing_, _radius_)
51
+ _Units_ = ( Easting, Northing, Radius)
52
+
53
+
54
+ class WebMercatorError(_ValueError):
55
+ '''Web Mercator (WM) parser or L{Wm} issue.
56
+ '''
57
+ pass
58
+
59
+
60
+ class Wm(_NamedBase):
61
+ '''Web Mercator (WM) coordinate.
62
+ '''
63
+ _datum = None # set further below
64
+ _earths = () # dito
65
+ _radius = R_MA # earth radius (C{meter})
66
+ _x = 0 # Easting (C{meter})
67
+ _y = 0 # Northing (C{meter})
68
+
69
+ def __init__(self, x, y, earth=R_MA, name=NN, **radius):
70
+ '''New L{Wm} Web Mercator (WM) coordinate.
71
+
72
+ @arg x: Easting from central meridian (C{meter}).
73
+ @arg y: Northing from equator (C{meter}).
74
+ @kwarg earth: Earth radius (C{meter}), datum or
75
+ ellipsoid (L{Datum}, L{a_f2Tuple},
76
+ L{Ellipsoid} or L{Ellipsoid2}).
77
+ @kwarg name: Optional name (C{str}).
78
+ @kwarg radius: DEPRECATED, use keyword argument B{C{earth}}.
79
+
80
+ @note: WM is strictly defined for spherical and WGS84
81
+ ellipsoidal earth models only.
82
+
83
+ @raise WebMercatorError: Invalid B{C{x}}, B{C{y}} or B{C{radius}}.
84
+ '''
85
+ self._x = Easting( x=x, Error=WebMercatorError)
86
+ self._y = Northing(y=y, Error=WebMercatorError)
87
+
88
+ R = radius.get(_radius_, earth)
89
+ if R not in Wm._earths:
90
+ self._datum = _datum(R, _radius_ if radius else _earth_)
91
+ self._radius = self.datum.ellipsoid.a
92
+
93
+ if name:
94
+ self.name = name
95
+
96
+ @Property_RO
97
+ def datum(self):
98
+ '''Get the datum (C{Datum}).
99
+ '''
100
+ return self._datum
101
+
102
+ @Property_RO
103
+ def ellipsoid(self):
104
+ '''Get the ellipsoid (C{Ellipsoid}).
105
+ '''
106
+ return self.datum.ellipsoid
107
+
108
+ @Property_RO
109
+ def latlon(self):
110
+ '''Get the lat- and longitude (L{LatLon2Tuple}).
111
+ '''
112
+ return self.latlon2()
113
+
114
+ def latlon2(self, datum=None):
115
+ '''Convert this WM coordinate to a lat- and longitude.
116
+
117
+ @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid},
118
+ L{Ellipsoid2} or L{a_f2Tuple}) or earth
119
+ radius (C{meter}), overriding this WM's
120
+ C{radius} and C{datum}.
121
+
122
+ @return: A L{LatLon2Tuple}C{(lat, lon)}.
123
+
124
+ @note: WM is strictly defined for spherical and WGS84
125
+ ellipsoidal earth models only.
126
+
127
+ @raise TypeError: Invalid or non-ellipsoidal B{C{datum}}.
128
+
129
+ @see: Method C{toLatLon} for other return types.
130
+ '''
131
+ d = self.datum if datum in (None, self.datum, self.radius) else _datum(datum)
132
+ E = d.ellipsoid
133
+ R = self.radius
134
+ x = self.x / R
135
+ y = atan(exp(self.y / R)) * _2_0 - PI_2
136
+ if E.es or E.a != R: # strictly, WGS84 only
137
+ # <https://Web.Archive.org/web/20141009142830/http://earth-info.nga.mil/
138
+ # GandG/wgs84/web_mercator/(U)%20NGA_SIG_0011_1.0.0_WEBMERC.pdf>
139
+ y = y / R # /= chokes PyChecker
140
+ y -= E.es_atanh(tanh(y))
141
+ y *= E.a
142
+ x *= E.a / R
143
+
144
+ return LatLon2Tuple(degrees90(y), degrees180(x), name=self.name)
145
+
146
+ def parse(self, strWM, name=NN):
147
+ '''Parse a string to a similar L{Wm} instance.
148
+
149
+ @arg strWM: The WM coordinate (C{str}), see
150
+ function L{parseWM}.
151
+ @kwarg name: Optional instance name (C{str}),
152
+ overriding this name.
153
+
154
+ @return: The similar instance (L{Wm}).
155
+
156
+ @raise WebMercatorError: Invalid B{C{strWM}}.
157
+ '''
158
+ return parseWM(strWM, radius=self.radius, Wm=self.classof,
159
+ name=name or self.name)
160
+
161
+ @deprecated_method
162
+ def parseWM(self, strWM, name=NN): # PYCHOK no cover
163
+ '''DEPRECATED, use method L{Wm.parse}.'''
164
+ return self.parse(strWM, name=name)
165
+
166
+ @Property_RO
167
+ def philam(self):
168
+ '''Get the lat- and longitude ((L{PhiLam2Tuple}).
169
+ '''
170
+ return PhiLam2Tuple(*map(radians, self.latlon), name=self.name)
171
+
172
+ @Property_RO
173
+ def radius(self):
174
+ '''Get the earth radius (C{meter}).
175
+ '''
176
+ return self._radius
177
+
178
+ @deprecated_method
179
+ def to2ll(self, datum=None): # PYCHOK no cover
180
+ '''DEPRECATED, use method C{latlon2}.
181
+
182
+ @return: A L{LatLon2Tuple}C{(lat, lon)}.
183
+ '''
184
+ return self.latlon2(datum=datum)
185
+
186
+ def toLatLon(self, LatLon=None, datum=None, **LatLon_kwds):
187
+ '''Convert this WM coordinate to a geodetic point.
188
+
189
+ @kwarg LatLon: Ellipsoidal or sphperical C{LatLon} class to
190
+ return the geodetic point (C{LatLon}) or C{None}.
191
+ @kwarg datum: Optional, datum (C{Datum}) overriding this WM's.
192
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}}
193
+ keyword arguments, ignored if
194
+ C{B{LatLon} is None}.
195
+
196
+ @return: This WM coordinate as B{C{LatLon}} or if
197
+ C{B{LatLon} is None} a L{LatLonDatum3Tuple}.
198
+
199
+ @raise TypeError: If B{C{LatLon}} and B{C{datum}} are
200
+ incompatible or if B{C{datum}} is
201
+ invalid.
202
+ '''
203
+ d = datum or self.datum
204
+ _xinstanceof(Datum, datum=d)
205
+ r = self.latlon2(datum=d)
206
+ r = LatLonDatum3Tuple(r.lat, r.lon, d, name=r.name) if LatLon is None else \
207
+ LatLon(r.lat, r.lon, **_xkwds(LatLon_kwds, datum=d, name=r.name))
208
+ return r
209
+
210
+ def toRepr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_, radius=False, **unused): # PYCHOK expected
211
+ '''Return a string representation of this WM coordinate.
212
+
213
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
214
+ @kwarg fmt: Enclosing backets format (C{str}).
215
+ @kwarg sep: Optional separator between name:value pairs (C{str}).
216
+ @kwarg radius: If C{True} include the radius (C{bool}) or
217
+ C{scalar} to override this WM's radius.
218
+
219
+ @return: This WM as "[x:meter, y:meter]" (C{str}) or as "[x:meter,
220
+ y:meter], radius:meter]" if B{C{radius}} is C{True} or
221
+ C{scalar}.
222
+
223
+ @raise WebMercatorError: Invalid B{C{radius}}.
224
+ '''
225
+ t = self.toStr(prec=prec, sep=None, radius=radius)
226
+ n = (_x_, _y_, _radius_)[:len(t)]
227
+ return _xzipairs(n, t, sep=sep, fmt=fmt)
228
+
229
+ def toStr(self, prec=3, fmt=Fmt.F, sep=_SPACE_, radius=False, **unused): # PYCHOK expected
230
+ '''Return a string representation of this WM coordinate.
231
+
232
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
233
+ @kwarg fmt: Optional C{float} format (C{letter}).
234
+ @kwarg sep: Optional separator to join (C{str}) or C{None}
235
+ to return an unjoined C{tuple} of C{str}s.
236
+ @kwarg radius: If C{True} include the radius (C{bool}) or
237
+ C{scalar} to override this WM's radius.
238
+
239
+ @return: This WM as "meter meter" (C{str}) or as "meter meter
240
+ radius" if B{C{radius}} is C{True} or C{scalar}.
241
+
242
+ @raise WebMercatorError: Invalid B{C{radius}}.
243
+ '''
244
+ fs = self.x, self.y
245
+ if _isRadius(radius):
246
+ fs += (radius,)
247
+ elif radius: # is True:
248
+ fs += (self.radius,)
249
+ elif radius not in (None, False):
250
+ raise WebMercatorError(radius=radius)
251
+ t = strs(fs, prec=prec)
252
+ return t if sep is None else sep.join(t)
253
+
254
+ @Property_RO
255
+ def x(self):
256
+ '''Get the easting (C{meter}).
257
+ '''
258
+ return self._x
259
+
260
+ @Property_RO
261
+ def y(self):
262
+ '''Get the northing (C{meter}).
263
+ '''
264
+ return self._y
265
+
266
+ Wm._datum = _spherical_datum(Wm._radius, name=Wm.__name__, raiser=_radius_) # PYCHOK defaults
267
+ Wm._earths = (Wm._radius, Wm._datum, Wm._datum.ellipsoid)
268
+
269
+
270
+ def _datum(earth, name=_datum_):
271
+ '''(INTERNAL) Make a datum from an C{earth} radius, datum or ellipsoid.
272
+ '''
273
+ if earth in Wm._earths:
274
+ return Wm._datum
275
+ try:
276
+ return _spherical_datum(earth, name=name)
277
+ except Exception as x:
278
+ raise WebMercatorError(name, earth, cause=x)
279
+
280
+
281
+ def parseWM(strWM, radius=R_MA, Wm=Wm, name=NN):
282
+ '''Parse a string C{"e n [r]"} representing a WM coordinate,
283
+ consisting of easting, northing and an optional radius.
284
+
285
+ @arg strWM: A WM coordinate (C{str}).
286
+ @kwarg radius: Optional earth radius (C{meter}), needed in
287
+ case B{C{strWM}} doesn't include C{r}.
288
+ @kwarg Wm: Optional class to return the WM coordinate (L{Wm})
289
+ or C{None}.
290
+ @kwarg name: Optional name (C{str}).
291
+
292
+ @return: The WM coordinate (B{C{Wm}}) or an
293
+ L{EasNorRadius3Tuple}C{(easting, northing, radius)}
294
+ if B{C{Wm}} is C{None}.
295
+
296
+ @raise WebMercatorError: Invalid B{C{strWM}}.
297
+ '''
298
+ def _WM(strWM, radius, Wm, name):
299
+ w = _splituple(strWM)
300
+
301
+ if len(w) == 2:
302
+ w += (radius,)
303
+ elif len(w) != 3:
304
+ raise ValueError
305
+ x, y, R = map(float, w)
306
+
307
+ return EasNorRadius3Tuple(x, y, R, name=name) if Wm is None else \
308
+ Wm(x, y, earth=R, name=name)
309
+
310
+ return _parseX(_WM, strWM, radius, Wm, name,
311
+ strWM=strWM, Error=WebMercatorError)
312
+
313
+
314
+ def toWm(latlon, lon=None, earth=R_MA, Wm=Wm, name=NN, **Wm_kwds):
315
+ '''Convert a lat-/longitude point to a WM coordinate.
316
+
317
+ @arg latlon: Latitude (C{degrees}) or an (ellipsoidal or
318
+ spherical) geodetic C{LatLon} point.
319
+ @kwarg lon: Optional longitude (C{degrees} or C{None}).
320
+ @kwarg earth: Earth radius (C{meter}), datum or ellipsoid
321
+ (L{Datum}, L{a_f2Tuple}, L{Ellipsoid} or
322
+ L{Ellipsoid2}), overridden by B{C{latlon}}'s
323
+ datum if present.
324
+ @kwarg Wm: Optional class to return the WM coordinate (L{Wm})
325
+ or C{None}.
326
+ @kwarg name: Optional name (C{str}).
327
+ @kwarg Wm_kwds: Optional, additional B{C{Wm}} keyword arguments,
328
+ ignored if C{B{Wm} is None}.
329
+
330
+ @return: The WM coordinate (B{C{Wm}}) or if B{C{Wm}} is C{None}
331
+ an L{EasNorRadius3Tuple}C{(easting, northing, radius)}.
332
+
333
+ @raise ValueError: If B{C{lon}} value is missing, if B{C{latlon}} is not
334
+ scalar, if B{C{latlon}} is beyond the valid WM range
335
+ and L{pygeodesy.rangerrors} is set to C{True} or if
336
+ B{C{earth}} is invalid.
337
+ '''
338
+ if _radius_ in Wm_kwds: # remove DEPRECATED, radius
339
+ d = _datum(Wm_kwds.pop(_radius_), _radius_)
340
+ else:
341
+ d = _datum(earth, _earth_)
342
+ try:
343
+ y, x = latlon.lat, latlon.lon
344
+ y = clipDegrees(y, _LatLimit)
345
+ d = _xattr(latlon, datum=d)
346
+ n = name or _xattr(latlon, name=NN)
347
+ except AttributeError:
348
+ y, x = parseDMS2(latlon, lon, clipLat=_LatLimit)
349
+ n = name
350
+
351
+ E = d.ellipsoid
352
+ R = E.a
353
+ s = sin(radians(y))
354
+ y = atanh(s) # == log(tand((90 + lat) / 2)) == log(tanPI_2_2(radians(lat)))
355
+ if E.es:
356
+ y -= E.es_atanh(s) # strictly, WGS84 only
357
+ y *= R
358
+ x = R * radians(x)
359
+ r = EasNorRadius3Tuple(x, y, R, name=n) if Wm is None else \
360
+ Wm(x, y, **_xkwds(Wm_kwds, earth=d, name=n))
361
+ return r
362
+
363
+ # **) MIT License
364
+ #
365
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
366
+ #
367
+ # Permission is hereby granted, free of charge, to any person obtaining a
368
+ # copy of this software and associated documentation files (the "Software"),
369
+ # to deal in the Software without restriction, including without limitation
370
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
371
+ # and/or sell copies of the Software, and to permit persons to whom the
372
+ # Software is furnished to do so, subject to the following conditions:
373
+ #
374
+ # The above copyright notice and this permission notice shall be included
375
+ # in all copies or substantial portions of the Software.
376
+ #
377
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
378
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
379
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
380
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
381
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
382
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
383
+ # OTHER DEALINGS IN THE SOFTWARE.