degrees 0.0.1__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.
degrees/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .degrees import Degree, degree2radius, radius2degree, convert_to_360
2
+
3
+ __all__ = ["Degree", "degree2radius", "radius2degree", "convert_to_360"]
degrees/degrees.py ADDED
@@ -0,0 +1,312 @@
1
+ from typing import Self, Any, NoReturn, overload
2
+ from math import radians, degrees
3
+
4
+
5
+ class Degree:
6
+ @overload
7
+ def __init__(self, degre: int = 0, minute: int = 0, second: int = 0) -> None: ...
8
+ @overload
9
+ def __init__(self, degre: float) -> None: ...
10
+ @overload
11
+ def __init__(self, degre: Degree) -> None: ...
12
+ def __init__(self, degre: int | float | Degree = 0,
13
+ minute: int = 0,
14
+ second: int = 0) -> None:
15
+ """Create a degree object"""
16
+ self.__set_: bool
17
+ self.deg: int
18
+ self.min: int
19
+ self.sec: int
20
+
21
+ if isinstance(degre, Degree):
22
+ if minute != 0 or second != 0:
23
+ raise ValueError("if the type of degree is Degree, minute and second must be 0")
24
+ self.deg = degre.deg
25
+ self.min = degre.min
26
+ self.sec = degre.sec
27
+ self.sign = degre.sign
28
+ self.__set_ = True
29
+ return
30
+ if degre != 0:
31
+ if minute < 0 or second < 0:
32
+ raise ValueError("if degree is not 0, minute and second must be positive integer")
33
+ self.sign = 1 if degre > 0 else -1
34
+ elif minute != 0:
35
+ if second < 0:
36
+ raise ValueError("if degree is 0, but minute is not, second must be positive integer")
37
+ self.sign = 1 if minute > 0 else -1
38
+ elif second != 0:
39
+ self.sign = 1 if second > 0 else -1
40
+ else:
41
+ self.sign = 0
42
+
43
+ # noinspection PyTypeChecker
44
+ degre = abs(degre) # type: ignore
45
+ minute = abs(minute)
46
+ second = abs(second)
47
+
48
+ self.deg = degre
49
+ self.min = minute
50
+ self.sec = second
51
+
52
+ if isinstance(degre, float):
53
+ self.deg = int(degre)
54
+ _ = abs(degre % 1)
55
+ self.min = int(_ * 60)
56
+ self.sec = int(_ * 3600 % 60)
57
+ self.sign = 1 if degre > 0 else -1
58
+
59
+ if isinstance(minute, float) or isinstance(second, float):
60
+ raise TypeError("if the type of degree is float, minute and second must be None")
61
+
62
+ if self.sec >= 60:
63
+ self.min += self.sec // 60
64
+ self.sec %= 60
65
+
66
+ if self.min >= 60:
67
+ if self.deg >= 0:
68
+ self.deg += self.min // 60
69
+ else:
70
+ self.deg -= self.min // 60
71
+ self.min %= 60
72
+
73
+ if not (isinstance(self.deg, int) and isinstance(self.min, int) and isinstance(self.sec, int)):
74
+ raise TypeError("unknown type")
75
+ self.__set_ = True
76
+
77
+ def __abs__(self) -> Self:
78
+ """Return the absolute value of the degree object"""
79
+ return Degree(abs(self.deg), abs(self.min), abs(self.sec))
80
+
81
+ def __str__(self) -> str:
82
+ """Return the string form of the degree object
83
+ Examples:
84
+ Degree(1) -> 1°
85
+ Degree(1, 3, 5) -> 1°3'5"
86
+ Degree(1, 0, 5) -> 1°0'5"
87
+ Degree(1, 3) -> 1°3'
88
+ ..."""
89
+ r: str = "" if self.sign > 0 else "-"
90
+ if self.deg == self.min == self.sec == 0:
91
+ return "0°"
92
+ if self.deg != 0 and self.sec != 0:
93
+ return r + f"{self.deg}°{self.min}'{self.sec}\""
94
+ if self.deg != 0:
95
+ r += f"{self.deg}°"
96
+ if self.min != 0:
97
+ r += f"{self.min}'"
98
+ if self.sec != 0:
99
+ r += f'{self.sec}"'
100
+ return r
101
+
102
+ def __repr__(self) -> str:
103
+ return str(self)
104
+
105
+ def __pos__(self) -> Self:
106
+ """Return self unchanged"""
107
+ return self
108
+
109
+ def __neg__(self) -> Self:
110
+ """Return self negated"""
111
+ if self.deg != 0:
112
+ return Degree(-self.deg, self.min, self.sec)
113
+ if self.min != 0:
114
+ return Degree(0, -self.min, self.sec)
115
+ return Degree(second=-self.sec)
116
+
117
+ def __ceil__(self) -> Self:
118
+ """Return the least Integral >= x"""
119
+ if self < Degree(0):
120
+ return int(self)
121
+ if self == Degree(0):
122
+ return Degree(0)
123
+ if not (self.min or self.sec):
124
+ return self.deg
125
+ return self.deg + 1
126
+
127
+ def __floor__(self) -> Self:
128
+ """Return the greatest Integral <= x"""
129
+ if self > Degree(0):
130
+ return int(self)
131
+ if self == Degree(0):
132
+ return Degree(0)
133
+ if not (self.min or self.sec):
134
+ return self.deg
135
+ return -self.deg - 1
136
+
137
+ def __int__(self) -> int:
138
+ """Return the integer form of the degree object"""
139
+ return self.deg * self.sign
140
+
141
+ def __float__(self) -> float:
142
+ """Return the float form of the degree object"""
143
+ number = (self.deg + self.min / 60 + self.sec / 3600) * self.sign
144
+ return float(int(number * 1000) / 1000)
145
+
146
+ def __bool__(self) -> bool:
147
+ """Return True if the degree object is not equal to zero, else false"""
148
+ if self == Degree(0):
149
+ return False
150
+ return True
151
+
152
+ def __eq__(self, other: Any) -> bool:
153
+ """Return self is equal to other"""
154
+ if isinstance(other, int):
155
+ return self.deg == other and self.min == 0 and self.sec == 0
156
+ if isinstance(other, Degree):
157
+ return (self.deg == other.deg and
158
+ self.min == other.min and
159
+ self.sec == other.sec)
160
+ return float(self) == other
161
+
162
+ def __gt__(self, other: Self | int | float) -> bool:
163
+ """Return self is strictly greater than other"""
164
+ if self == other:
165
+ return False
166
+ if isinstance(other, Degree):
167
+ if self.sign > other.sign:
168
+ return True
169
+ if self.sign < other.sign:
170
+ return False
171
+ if self.sign + other.sign == -2:
172
+ if self.deg > other.deg:
173
+ return False
174
+ if self.deg < other.deg:
175
+ return True
176
+ if self.min > other.min:
177
+ return False
178
+ if self.min < other.min:
179
+ return True
180
+ return self.sec < other.sec
181
+ if self.sign + other.sign == 2:
182
+ if self.deg < other.deg:
183
+ return False
184
+ if self.deg > other.deg:
185
+ return True
186
+ if self.min < other.min:
187
+ return False
188
+ if self.min > other.min:
189
+ return True
190
+ return self.sec > other.sec
191
+ return False
192
+ if isinstance(other, int):
193
+ return int(self) > other
194
+ if isinstance(other, float):
195
+ return float(self) > other
196
+ raise TypeError("invalid type")
197
+
198
+ def __lt__(self, other: Self | int | float) -> bool:
199
+ """Return self is strictly less than other"""
200
+ return not self >= other
201
+
202
+ def __ge__(self, other: Self | int | float) -> bool:
203
+ """Return self is greater than or equal to other"""
204
+ return self > other or self == other
205
+
206
+ def __le__(self, other: Self | int | float) -> bool:
207
+ """Return self is less than or equal to other"""
208
+ return not self > other
209
+
210
+ def __ne__(self, other: Any) -> bool:
211
+ """Return self is not equal to other"""
212
+ return not self == other
213
+
214
+ def __add__(self, other: Self | int | float) -> Self:
215
+ """Return the sum of self and other"""
216
+ oth: Degree = Degree(other)
217
+ ttsec: int = ((self.deg * 3600 + self.min * 60 + self.sec) * self.sign +
218
+ (oth.deg * 3600 + oth.min * 60 + oth.sec) * oth.sign)
219
+ return Degree(second=ttsec)
220
+
221
+ def __radd__(self, other: Self | int | float) -> Self:
222
+ """Return the sum of other and self"""
223
+ return self + other
224
+
225
+ def __sub__(self, other: Self | int | float) -> Self:
226
+ """Return the difference of self and other"""
227
+ return self + -other
228
+
229
+ def __rsub__(self, other: Self | int | float) -> Self:
230
+ """Return the difference of other and self"""
231
+ return -(self.__sub__(other))
232
+
233
+ def __mul__(self, other: Self | int | float) -> Self:
234
+ """Return the product of self and other"""
235
+ if isinstance(other, (int, float)):
236
+ tts = (self.deg * 3600 + self.min * 60 + self.sec) * self.sign * other
237
+ return Degree(second=tts)
238
+ srad = radians(self.deg + self.min / 60 + self.sec / 3600) * self.sign
239
+ orad = radians(other.deg + other.min / 60 + other.sec / 3600) * other.sign
240
+ resrad = srad * orad
241
+ resdeg = degrees(resrad)
242
+ return Degree(resdeg)
243
+
244
+ def __rmul__(self, other: Self | int | float) -> Self:
245
+ """Return the product of other and self"""
246
+ return self * other
247
+
248
+ @overload
249
+ def __truediv__(self, other: Self) -> float: ...
250
+ @overload
251
+ def __truediv__(self, other: int | float) -> Self: ...
252
+ def __truediv__(self, other: Self | int | float) -> Self | float:
253
+ """Return the quotient of self and other"""
254
+ if other == 0:
255
+ raise ZeroDivisionError("division by zero")
256
+ if isinstance(other, (int, float)):
257
+ tts = (self.deg * 3600 + self.min * 60 + self.sec) * self.sign // other
258
+ return Degree(second=tts)
259
+ sts = (self.deg * 3600 + self.min * 60 + self.sec) * self.sign
260
+ ots = (other.deg * 3600 + other.min * 60 + other.sec) * other.sign
261
+ return sts / ots
262
+
263
+ @overload
264
+ def __floordiv__(self, other: Self) -> float: ...
265
+ @overload
266
+ def __floordiv__(self, other: int | float) -> Self: ...
267
+ def __floordiv__(self, other: Self | int | float) -> Self | int:
268
+ """Return the floored quotient of self and other"""
269
+ if other == 0:
270
+ raise ZeroDivisionError("division by zero")
271
+ if isinstance(other, (int, float)):
272
+ res = self.deg * self.sign // other
273
+ return Degree(res)
274
+ sts = (self.deg * 3600 + self.min * 60 + self.sec) * self.sign
275
+ ots = (other.deg * 3600 + other.min * 60 + other.sec) * other.sign
276
+ return sts // ots
277
+
278
+ def __mod__(self, other: Self) -> Self:
279
+ """Return the remainder of 'self / other'"""
280
+ if other == Degree(0):
281
+ raise ZeroDivisionError("modulo by zero")
282
+ sts = (self.deg * 3600 + self.min * 60 + self.sec) * self.sign
283
+ ots = (other.deg * 3600 + other.min * 60 + other.sec) * other.sign
284
+ rs = sts % ots
285
+ return Degree(second=rs)
286
+
287
+ def __hash__(self) -> int:
288
+ """Return the hash value of the degree object"""
289
+ return hash(id(self))
290
+
291
+ def __setattr__(self, key: str, value: Any) -> None | NoReturn:
292
+ """Do NOT use this function, it is only used for creating a degree object"""
293
+ if hasattr(self, '_Degree__set_'):
294
+ raise AttributeError("readonly attribute")
295
+ else:
296
+ super().__setattr__(key, value)
297
+
298
+ def degree2radius(x: Degree, /) -> float:
299
+ """Convert angle x from a degree object to radians"""
300
+ return radians(x.deg + x.min / 60 + x.sec / 3600)
301
+
302
+ def radius2degree(x: int | float, /) -> Degree:
303
+ """Convert angle x from radians to a degree object"""
304
+ return Degree(degrees(x))
305
+
306
+ def convert_to_360(x: Degree, /) -> Degree:
307
+ """Be using for angle normalization"""
308
+ tts = (x.deg * 3600 + x.min * 60 + x.sec) * x.sign
309
+ norms = tts % 1_296_000
310
+ if norms < 0:
311
+ norms += 1_296_000
312
+ return Degree(second=int(norms))
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: degrees
3
+ Version: 0.0.1
4
+ Summary: A python lib for degree calculations and conversions
5
+ Author-email: ArcticChar <snake830@vip.163.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ <!-- TOC -->
15
+ * [Introduction](#introduction)
16
+ * [Class](#class)
17
+ * [Functions](#functions)
18
+ * [degree2radius](#def-degree2radiusx-degree-)
19
+ * [radius2degree](#def-radius2degreex-int--float-)
20
+ * [convert_to_360](#def-convert_to_360x-degree-)
21
+ <!-- TOC -->
22
+ # Introduction
23
+ A python lib for degree calculations and conversions
24
+ # Class
25
+ - ## *class* degrees.Degree(degre: float)
26
+ ## *class* degrees.Degree(degre: Degree)
27
+ ## *class* degrees.Degree(degre: int = 0, minute: int = 0, second: int = 0)
28
+ - ### Creating a Degree object
29
+ ```python
30
+ >>> degrees.Degree(1)
31
+
32
+ >>> degrees.Degree(2, 3, 4)
33
+ 2°3'4"
34
+ >>> degrees.Degree(1, second=2)
35
+ 1°0'2"
36
+ >>> degrees.Degree(1, 3)
37
+ 1°3'
38
+ ```
39
+ - ### calculating:
40
+
41
+ | `a + b` | `a - b` | `a * b` | `a / b` |
42
+ |----------|----------------|-----------------|-----------------------|
43
+ | `abs(a)` | `math.ceil(a)` | `math.floor(a)` | `a % b` |
44
+ | `a // b` | `+a` | `-a` | `hash(a)`<sup>1</sup> |
45
+ - ### conversions:
46
+
47
+ | `int(a)` | `float(a)` | `str(a)` | `repr(a)` | `bool(a)` |
48
+ |----------|------------|----------|-----------|-----------|
49
+ - ### comparisons:
50
+
51
+ | `a >= b` | `a > b` | `a = b` |
52
+ |----------|---------|----------|
53
+ | `a <= b` | `a < b` | `a != b` |
54
+
55
+ > ### Note
56
+ > [1] `hash(Degree_object)` is equal to `hash(int(Degree_object)).`<br>
57
+ > [2] The attributes of Degree are read-only.
58
+ # Functions
59
+ ## *def* degree2radius(x: Degree, /)
60
+ - Convert angle x from a degree object to radians.
61
+ ## *def* radius2degree(x: int | float, /)
62
+ - Convert angle x from radians to a degree object.
63
+ ## *def* convert_to_360(x: Degree, /)
64
+ - Be using for angle normalization.
@@ -0,0 +1,7 @@
1
+ degrees/__init__.py,sha256=2JfPtemB6jvAoYM_3Nz_IE0NRaJyI8IPwwz1-s0Ea5I,147
2
+ degrees/degrees.py,sha256=vUz1I2TmxaOVYFy36Go5dDdYbqwdENQi39otnkPG6Gc,11676
3
+ degrees-0.0.1.dist-info/licenses/LICENSE,sha256=oRnScbx7lo8OJ1qmfJ5vtSxh4Yah62edFNh0yveODrk,1053
4
+ degrees-0.0.1.dist-info/METADATA,sha256=eNv9hu3l2cmnSmb235rXRIBw1OO1PhcwT1smhTdES3I,2221
5
+ degrees-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ degrees-0.0.1.dist-info/top_level.txt,sha256=qY1E6fjp-KQmh3bW5meeW_FbGURMfk4I6jTVW0KB0HM,8
7
+ degrees-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 ArcticChar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ degrees