apsg 1.3.0__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.
- AUTHORS.md +9 -0
- CHANGELOG.md +304 -0
- CONTRIBUTING.md +91 -0
- apsg/__init__.py +104 -0
- apsg/config.py +214 -0
- apsg/database/__init__.py +23 -0
- apsg/database/_alchemy.py +609 -0
- apsg/database/_sdbread.py +284 -0
- apsg/decorator/__init__.py +5 -0
- apsg/decorator/_decorator.py +43 -0
- apsg/feature/__init__.py +79 -0
- apsg/feature/_container.py +1808 -0
- apsg/feature/_geodata.py +702 -0
- apsg/feature/_paleomag.py +425 -0
- apsg/feature/_statistics.py +430 -0
- apsg/feature/_tensor2.py +550 -0
- apsg/feature/_tensor3.py +1108 -0
- apsg/helpers/__init__.py +28 -0
- apsg/helpers/_helper.py +7 -0
- apsg/helpers/_math.py +46 -0
- apsg/helpers/_notation.py +119 -0
- apsg/math/__init__.py +6 -0
- apsg/math/_matrix.py +406 -0
- apsg/math/_vector.py +590 -0
- apsg/pandas/__init__.py +27 -0
- apsg/pandas/_pandas_api.py +507 -0
- apsg/plotting/__init__.py +25 -0
- apsg/plotting/_fabricplot.py +563 -0
- apsg/plotting/_paleomagplots.py +71 -0
- apsg/plotting/_plot_artists.py +551 -0
- apsg/plotting/_projection.py +326 -0
- apsg/plotting/_roseplot.py +360 -0
- apsg/plotting/_stereogrid.py +332 -0
- apsg/plotting/_stereonet.py +992 -0
- apsg/shell.py +35 -0
- apsg-1.3.0.dist-info/AUTHORS.md +9 -0
- apsg-1.3.0.dist-info/METADATA +141 -0
- apsg-1.3.0.dist-info/RECORD +40 -0
- apsg-1.3.0.dist-info/WHEEL +4 -0
- apsg-1.3.0.dist-info/entry_points.txt +3 -0
apsg/feature/_geodata.py
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from apsg.helpers._notation import (
|
|
5
|
+
geo2vec_planar,
|
|
6
|
+
vec2geo_planar,
|
|
7
|
+
vec2geo_planar_signed,
|
|
8
|
+
vec2geo_linear,
|
|
9
|
+
vec2geo_linear_signed,
|
|
10
|
+
)
|
|
11
|
+
from apsg.decorator._decorator import ensure_first_arg_same, ensure_arguments
|
|
12
|
+
from apsg.math._vector import Vector3, Axial3
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Lineation(Axial3):
|
|
16
|
+
"""
|
|
17
|
+
A class to represent axial (non-oriented) linear feature (lineation).
|
|
18
|
+
|
|
19
|
+
There are different way to create ``Lineation`` object:
|
|
20
|
+
|
|
21
|
+
- without arguments create default ``Lineation`` L:0/0
|
|
22
|
+
- with single argument `l`, where:
|
|
23
|
+
|
|
24
|
+
- `l` could be Vector3-like object
|
|
25
|
+
- `l` could be string 'x', 'y' or 'z' - principal axes of coordinate system
|
|
26
|
+
- `l` could be tuple of (x, y, z) - vector components
|
|
27
|
+
- with 2 arguments plunge direction and plunge
|
|
28
|
+
- with 3 numerical arguments defining vector components
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
azi (float): plunge direction of linear feature in degrees
|
|
32
|
+
inc (float): plunge of linear feature in degrees
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
>>> lin()
|
|
36
|
+
>>> lin('y')
|
|
37
|
+
>>> lin(1,2,-1)
|
|
38
|
+
>>> l = lin(110, 26)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __repr__(self):
|
|
42
|
+
azi, inc = self.geo
|
|
43
|
+
return f"L:{azi:.0f}/{inc:.0f}"
|
|
44
|
+
|
|
45
|
+
def cross(self, other):
|
|
46
|
+
"""Return Foliation defined by two linear features"""
|
|
47
|
+
return Foliation(super().cross(other))
|
|
48
|
+
|
|
49
|
+
__pow__ = cross
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def geo(self):
|
|
53
|
+
"""Return tuple of plunge direction and plunge"""
|
|
54
|
+
return vec2geo_linear(self)
|
|
55
|
+
|
|
56
|
+
def to_json(self):
|
|
57
|
+
"""Return as JSON dict"""
|
|
58
|
+
azi, inc = vec2geo_linear_signed(self)
|
|
59
|
+
return {"datatype": type(self).__name__, "args": (azi, inc)}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Foliation(Axial3):
|
|
63
|
+
"""
|
|
64
|
+
A class to represent non-oriented planar feature (foliation).
|
|
65
|
+
|
|
66
|
+
There are different way to create ``Foliation`` object:
|
|
67
|
+
|
|
68
|
+
- without arguments create default ``Foliation`` S:180/0
|
|
69
|
+
- with single argument `f`, where:
|
|
70
|
+
|
|
71
|
+
- `f` could be Vector3-like object
|
|
72
|
+
- `f` could be string 'x', 'y' or 'z' - principal planes of coordinate system
|
|
73
|
+
- `f` could be tuple of (x, y, z) - vector components
|
|
74
|
+
- with 2 arguments follows active notation. See apsg_conf["notation"]
|
|
75
|
+
- with 3 numerical arguments defining vector components of plane normal
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
azi (float): dip direction (or strike) of planar feature in degrees.
|
|
79
|
+
inc (float): dip of planar feature in degrees
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> fol()
|
|
83
|
+
>>> fol('y')
|
|
84
|
+
>>> fol(1,2,-1)
|
|
85
|
+
>>> f = fol(250, 30)
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, *args):
|
|
90
|
+
if len(args) == 0:
|
|
91
|
+
coords = (0, 0, 1)
|
|
92
|
+
elif len(args) == 1:
|
|
93
|
+
if np.asarray(args[0]).shape == Foliation.__shape__:
|
|
94
|
+
coords = np.asarray(args[0])
|
|
95
|
+
elif isinstance(args[0], str):
|
|
96
|
+
if args[0].lower() == "x":
|
|
97
|
+
coords = (1, 0, 0)
|
|
98
|
+
elif args[0].lower() == "y":
|
|
99
|
+
coords = (0, 1, 0)
|
|
100
|
+
elif args[0].lower() == "z":
|
|
101
|
+
coords = (0, 0, 1)
|
|
102
|
+
else:
|
|
103
|
+
raise TypeError(f"Not valid arguments for {type(self).__name__}")
|
|
104
|
+
else:
|
|
105
|
+
raise TypeError(f"Not valid arguments for {type(self).__name__}")
|
|
106
|
+
elif len(args) == 2:
|
|
107
|
+
coords = geo2vec_planar(*args)
|
|
108
|
+
elif len(args) == 3:
|
|
109
|
+
coords = [float(v) for v in args]
|
|
110
|
+
else:
|
|
111
|
+
raise TypeError("Not valid arguments for Foliation")
|
|
112
|
+
self._coords = tuple(coords)
|
|
113
|
+
|
|
114
|
+
def __repr__(self):
|
|
115
|
+
azi, inc = self.geo
|
|
116
|
+
return f"S:{azi:.0f}/{inc:.0f}"
|
|
117
|
+
|
|
118
|
+
def cross(self, other):
|
|
119
|
+
"""Return Lineation defined by intersection of planar features"""
|
|
120
|
+
return Lineation(super().cross(other))
|
|
121
|
+
|
|
122
|
+
__pow__ = cross
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def geo(self):
|
|
126
|
+
"""Return tuple of dip direction and dip"""
|
|
127
|
+
return vec2geo_planar(self)
|
|
128
|
+
|
|
129
|
+
def to_json(self):
|
|
130
|
+
"""Return as JSON dict"""
|
|
131
|
+
azi, inc = vec2geo_planar_signed(self)
|
|
132
|
+
return {"datatype": type(self).__name__, "args": (azi, inc)}
|
|
133
|
+
|
|
134
|
+
def dipvec(self):
|
|
135
|
+
"""Return dip vector"""
|
|
136
|
+
return Vector3(*vec2geo_planar(self))
|
|
137
|
+
|
|
138
|
+
def pole(self):
|
|
139
|
+
"""Return plane normal as vector"""
|
|
140
|
+
return Vector3(self)
|
|
141
|
+
|
|
142
|
+
def rake(self, rake):
|
|
143
|
+
"""Return rake vector"""
|
|
144
|
+
return Vector3(self.dipvec().rotate(self, rake - 90))
|
|
145
|
+
|
|
146
|
+
def transform(self, F, **kwargs):
|
|
147
|
+
"""
|
|
148
|
+
Return affine transformation by matrix `F`.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
F: transformation matrix
|
|
152
|
+
|
|
153
|
+
Keyword Args:
|
|
154
|
+
norm: normalize transformed ``Foliation``. [True or False] Default False
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
vector representation of affine transformation (dot product)
|
|
158
|
+
of `self` by `F`
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> # Reflexion of `y` axis.
|
|
162
|
+
>>> F = [[1, 0, 0], [0, -1, 0], [0, 0, 1]]
|
|
163
|
+
>>> f = fol(45, 20)
|
|
164
|
+
>>> f.transform(F)
|
|
165
|
+
S:315/20
|
|
166
|
+
"""
|
|
167
|
+
r = np.dot(self, np.linalg.inv(F))
|
|
168
|
+
if kwargs.get("norm", False):
|
|
169
|
+
r = r.normalized()
|
|
170
|
+
return type(self)(r)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class Pair:
|
|
174
|
+
"""
|
|
175
|
+
The class to store pair of planar and linear feature.
|
|
176
|
+
|
|
177
|
+
When ``Pair`` object is created, both planar and linear feature are
|
|
178
|
+
adjusted, so linear feature perfectly fit onto planar one. Warning
|
|
179
|
+
is issued, when misfit angle is bigger than 20 degrees.
|
|
180
|
+
|
|
181
|
+
There are different way to create ``Pair`` object:
|
|
182
|
+
|
|
183
|
+
- without arguments create default Pair with fol(0,0) and lin(0,0)
|
|
184
|
+
- with single argument `p`, where:
|
|
185
|
+
|
|
186
|
+
- `p` could be Pair
|
|
187
|
+
- `p` could be tuple of (fazi, finc, lazi, linc)
|
|
188
|
+
- `p` could be tuple of (fx, fy ,fz, lx, ly, lz)
|
|
189
|
+
- with 2 arguments f and l could be Vector3 like objects,
|
|
190
|
+
e.g. Foliation and Lineation
|
|
191
|
+
- with four numerical arguments defining `fol(fazi, finc)` and `lin(lazi, linc)`
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
fazi (float): dip azimuth of planar feature in degrees
|
|
195
|
+
finc (float): dip of planar feature in degrees
|
|
196
|
+
lazi (float): plunge direction of linear feature in degrees
|
|
197
|
+
linc (float): plunge of linear feature in degrees
|
|
198
|
+
|
|
199
|
+
Attributes:
|
|
200
|
+
fvec (Vector3): corrected vector normal to plane
|
|
201
|
+
lvec (Vector3): corrected vector of linear feature
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> pair()
|
|
205
|
+
>>> pair(p)
|
|
206
|
+
>>> pair(f, l)
|
|
207
|
+
>>> pair(fazi, finc, lazi, linc)
|
|
208
|
+
>>> p = pair(140, 30, 110, 26)
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
__slots__ = ("fvec", "lvec", "misfit")
|
|
213
|
+
__shape__ = (6,)
|
|
214
|
+
|
|
215
|
+
def __init__(self, *args):
|
|
216
|
+
if len(args) == 0:
|
|
217
|
+
fvec, lvec = Vector3(0, 0, 1), Vector3(1, 0, 0)
|
|
218
|
+
elif len(args) == 1 and issubclass(type(args[0]), Pair):
|
|
219
|
+
fvec, lvec = args[0].fvec, args[0].lvec
|
|
220
|
+
elif len(args) == 1 and np.asarray(args[0]).shape == (4,):
|
|
221
|
+
fazi, finc, lazi, linc = (float(v) for v in args[0])
|
|
222
|
+
fvec, lvec = Foliation(fazi, finc), Lineation(lazi, linc)
|
|
223
|
+
elif len(args) == 1 and np.asarray(args[0]).shape == Pair.__shape__:
|
|
224
|
+
fvec, lvec = Vector3(args[0][:3]), Vector3(args[0][-3:])
|
|
225
|
+
elif len(args) == 2:
|
|
226
|
+
if issubclass(type(args[0]), Vector3) and issubclass(
|
|
227
|
+
type(args[1]), Vector3
|
|
228
|
+
):
|
|
229
|
+
fvec, lvec = args
|
|
230
|
+
else:
|
|
231
|
+
raise TypeError("Not valid arguments for Pair")
|
|
232
|
+
elif len(args) == 4:
|
|
233
|
+
fvec = Foliation(args[0], args[1])
|
|
234
|
+
lvec = Lineation(args[2], args[3])
|
|
235
|
+
else:
|
|
236
|
+
raise TypeError("Not valid arguments for Pair")
|
|
237
|
+
|
|
238
|
+
fvec = Vector3(fvec)
|
|
239
|
+
lvec = Vector3(lvec)
|
|
240
|
+
misfit = abs(90 - fvec.angle(lvec))
|
|
241
|
+
if misfit > 20:
|
|
242
|
+
warnings.warn(f"Warning: Misfit angle is {misfit:.1f} degrees.")
|
|
243
|
+
ax = fvec.cross(lvec)
|
|
244
|
+
ang = (lvec.angle(fvec) - 90) / 2
|
|
245
|
+
self.fvec = Vector3(fvec.rotate(ax, ang))
|
|
246
|
+
self.lvec = Vector3(lvec.rotate(ax, -ang))
|
|
247
|
+
self.misfit = misfit
|
|
248
|
+
|
|
249
|
+
def __repr__(self):
|
|
250
|
+
fazi, finc = self.fol.geo
|
|
251
|
+
lazi, linc = self.lin.geo
|
|
252
|
+
return f"P:{fazi:.0f}/{finc:.0f}-{lazi:.0f}/{linc:.0f}"
|
|
253
|
+
|
|
254
|
+
@ensure_first_arg_same
|
|
255
|
+
def __eq__(self, other):
|
|
256
|
+
"""
|
|
257
|
+
Return `True` if pairs are equal, otherwise `False`.
|
|
258
|
+
"""
|
|
259
|
+
return (self.fvec == other.fvec) and (self.lvec == other.lvec)
|
|
260
|
+
|
|
261
|
+
def __ne__(self, other):
|
|
262
|
+
"""
|
|
263
|
+
Return `True` if pairs are not equal, otherwise `False`.
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
return not self == other
|
|
267
|
+
|
|
268
|
+
def __array__(self, dtype=None, copy=None):
|
|
269
|
+
return np.hstack((self.fvec, self.lvec)).astype(dtype)
|
|
270
|
+
|
|
271
|
+
def label(self):
|
|
272
|
+
"""Return label"""
|
|
273
|
+
return str(self)
|
|
274
|
+
|
|
275
|
+
def to_json(self):
|
|
276
|
+
"""Return as JSON dict"""
|
|
277
|
+
fazi, finc = vec2geo_planar_signed(self.fvec)
|
|
278
|
+
lazi, linc = vec2geo_linear_signed(self.lvec)
|
|
279
|
+
return {"datatype": type(self).__name__, "args": (fazi, finc, lazi, linc)}
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def random(cls):
|
|
283
|
+
"""
|
|
284
|
+
Random Pair
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
lin, p = Vector3.random(), Vector3.random()
|
|
288
|
+
fol = lin.cross(p)
|
|
289
|
+
return cls(fol, lin)
|
|
290
|
+
|
|
291
|
+
@ensure_arguments(Vector3)
|
|
292
|
+
def rotate(self, axis, phi):
|
|
293
|
+
"""Rotates ``Pair`` by angle `phi` about `axis`.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
axis (``Vector3``): axis of rotation
|
|
297
|
+
phi (float): angle of rotation in degrees
|
|
298
|
+
|
|
299
|
+
Example:
|
|
300
|
+
>>> p = pair(fol(140, 30), lin(110, 26))
|
|
301
|
+
>>> p.rotate(lin(40, 50), 120)
|
|
302
|
+
P:210/83-287/60
|
|
303
|
+
|
|
304
|
+
"""
|
|
305
|
+
return type(self)(self.fvec.rotate(axis, phi), self.lvec.rotate(axis, phi))
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def rax(self):
|
|
309
|
+
return self.lvec.cross(self.fvec)
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def fol(self):
|
|
313
|
+
"""
|
|
314
|
+
Return a planar feature of ``Pair`` as ``Foliation``.
|
|
315
|
+
"""
|
|
316
|
+
return Foliation(self.fvec)
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def lin(self):
|
|
320
|
+
"""
|
|
321
|
+
Return a linear feature of ``Pair`` as ``Lineation``.
|
|
322
|
+
"""
|
|
323
|
+
return Lineation(self.lvec)
|
|
324
|
+
|
|
325
|
+
def transform(self, F, **kwargs):
|
|
326
|
+
"""Return an affine transformation of ``Pair`` by matrix `F`.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
F: transformation matrix
|
|
330
|
+
|
|
331
|
+
Keyword Args:
|
|
332
|
+
norm: normalize transformed vectors. True or False. Default False
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
representation of affine transformation (dot product) of `self`
|
|
336
|
+
by `F`
|
|
337
|
+
|
|
338
|
+
Example:
|
|
339
|
+
>>> F = defgrad.from_axisangle(lin(0,0), 60)
|
|
340
|
+
>>> p = pair(90, 90, 0, 50)
|
|
341
|
+
>>> p.transform(F)
|
|
342
|
+
P:270/30-314/23
|
|
343
|
+
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
fvec = self.fol.transform(F)
|
|
347
|
+
lvec = self.lin.transform(F)
|
|
348
|
+
if kwargs.get("norm", False):
|
|
349
|
+
fvec = fvec.normalized()
|
|
350
|
+
lvec = lvec.normalized()
|
|
351
|
+
return type(self)(fvec, lvec)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class Fault(Pair):
|
|
355
|
+
"""
|
|
356
|
+
The class to store ``Pair`` with associated sense of movement.
|
|
357
|
+
|
|
358
|
+
When ``Fault`` object is created, both planar and linear feature are
|
|
359
|
+
adjusted, so linear feature perfectly fit onto planar one. Warning
|
|
360
|
+
is issued, when misfit angle is bigger than 20 degrees.
|
|
361
|
+
|
|
362
|
+
There are different way to create ``Fault`` object:
|
|
363
|
+
|
|
364
|
+
- without arguments create default ``Fault`` with `fol(0,0)` and `lin(0,0)`
|
|
365
|
+
- with single argument `p`:
|
|
366
|
+
|
|
367
|
+
- `p` could be Fault
|
|
368
|
+
- `p` could be tuple of (fazi, finc, lazi, linc, sense)
|
|
369
|
+
- `p` could be tuple of (fx, fy ,fz, lx, ly, lz)
|
|
370
|
+
- with 2 arguments p (Pair object) and sense
|
|
371
|
+
- with 3 arguments f, l (Vector3 like objects), e.g. ``Foliation``
|
|
372
|
+
and ``Lineation`` and sense
|
|
373
|
+
- with 5 numerical arguments defining `fol(fazi, finc)`, `lin(lazi, linc)` and sense
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
fazi (float): dip azimuth of planar feature in degrees
|
|
377
|
+
finc (float): dip of planar feature in degrees
|
|
378
|
+
lazi (float): plunge direction of linear feature in degrees
|
|
379
|
+
linc (float): plunge of linear feature in degrees
|
|
380
|
+
sense (float or str): sense of movement +/11 hanging-wall down/up. When str,
|
|
381
|
+
,ust be one of 's', 'd', 'n', 'r'.
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
Attributes:
|
|
385
|
+
fvec (Vector3): corrected vector normal to plane
|
|
386
|
+
lvec (Vector3): corrected vector of linear feature
|
|
387
|
+
sense (int): sense of movement (+/-1)
|
|
388
|
+
|
|
389
|
+
Example:
|
|
390
|
+
>>> f = fault(140, 30, 110, 26, -1)
|
|
391
|
+
>>> f = fault(140, 30, 110, 26, 'r')
|
|
392
|
+
>>> p = pair(140, 30, 110, 26)
|
|
393
|
+
>>> f = fault(p, 'n')
|
|
394
|
+
>>> f = fault(fol(120, 80), lin(32, 10), 's')
|
|
395
|
+
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
__shape__ = (7,)
|
|
399
|
+
|
|
400
|
+
def __init__(self, *args):
|
|
401
|
+
if len(args) == 0:
|
|
402
|
+
fvec, lvec = Vector3(0, 0, 1), Vector3(1, 0, 0)
|
|
403
|
+
elif len(args) == 1 and np.asarray(args[0]).shape == (5,):
|
|
404
|
+
fazi, finc, lazi, linc, sense = (float(v) for v in args[0])
|
|
405
|
+
fvec, lvec = Foliation(fazi, finc), Lineation(lazi, linc)
|
|
406
|
+
sense = self.calc_sense(fvec, lvec, sense)
|
|
407
|
+
if sense < 0:
|
|
408
|
+
lvec = -lvec
|
|
409
|
+
elif len(args) == 1 and issubclass(type(args[0]), Pair):
|
|
410
|
+
fvec, lvec = args[0].fvec, args[0].lvec
|
|
411
|
+
elif len(args) == 2 and issubclass(type(args[0]), Pair):
|
|
412
|
+
fvec, lvec = args[0].fvec, args[0].lvec
|
|
413
|
+
sense = self.calc_sense(fvec, lvec, args[1])
|
|
414
|
+
georax = lvec.lower().cross(fvec.lower())
|
|
415
|
+
if args[0].rax == georax and sense < 0:
|
|
416
|
+
lvec = -lvec
|
|
417
|
+
elif len(args) == 2:
|
|
418
|
+
if issubclass(type(args[0]), Vector3) and issubclass(
|
|
419
|
+
type(args[1]), Vector3
|
|
420
|
+
):
|
|
421
|
+
fvec, lvec = args[0], args[1]
|
|
422
|
+
elif len(args) == 3:
|
|
423
|
+
if issubclass(type(args[0]), Vector3) and issubclass(
|
|
424
|
+
type(args[1]), Vector3
|
|
425
|
+
):
|
|
426
|
+
fvec, lvec = args[0], args[1]
|
|
427
|
+
sense = self.calc_sense(fvec, lvec, args[2])
|
|
428
|
+
rax = lvec.cross(fvec)
|
|
429
|
+
georax = lvec.lower().cross(fvec.lower())
|
|
430
|
+
if rax == georax and sense < 0:
|
|
431
|
+
lvec = -lvec
|
|
432
|
+
elif len(args) == 5:
|
|
433
|
+
fvec = Foliation(args[0], args[1])
|
|
434
|
+
lvec = Lineation(args[2], args[3])
|
|
435
|
+
sense = self.calc_sense(fvec, lvec, args[4])
|
|
436
|
+
if sense < 0:
|
|
437
|
+
lvec = -lvec
|
|
438
|
+
else:
|
|
439
|
+
raise TypeError("Not valid arguments for Fault")
|
|
440
|
+
super().__init__(fvec, lvec)
|
|
441
|
+
|
|
442
|
+
@classmethod
|
|
443
|
+
def calc_sense(cls, fvec, lvec, sense):
|
|
444
|
+
if isinstance(sense, int) or isinstance(sense, float):
|
|
445
|
+
return sense
|
|
446
|
+
elif isinstance(sense, str):
|
|
447
|
+
p = Pair(fvec, lvec)
|
|
448
|
+
if sense.lower() == "s":
|
|
449
|
+
if p.rax == p.rax.lower():
|
|
450
|
+
res = -1
|
|
451
|
+
else:
|
|
452
|
+
res = 1
|
|
453
|
+
elif sense.lower() == "d":
|
|
454
|
+
if p.rax == p.rax.lower():
|
|
455
|
+
res = 1
|
|
456
|
+
else:
|
|
457
|
+
res = -1
|
|
458
|
+
elif sense.lower() == "n":
|
|
459
|
+
res = 1
|
|
460
|
+
elif sense.lower() == "r":
|
|
461
|
+
res = -1
|
|
462
|
+
return res
|
|
463
|
+
|
|
464
|
+
def __repr__(self):
|
|
465
|
+
fazi, finc = self.fol.geo
|
|
466
|
+
lazi, linc = self.lin.geo
|
|
467
|
+
schar = [" ", "+", "-"][self.sense]
|
|
468
|
+
return f"F:{fazi:.0f}/{finc:.0f}-{lazi:.0f}/{linc:.0f} {schar}"
|
|
469
|
+
|
|
470
|
+
@ensure_first_arg_same
|
|
471
|
+
def __eq__(self, other):
|
|
472
|
+
"""
|
|
473
|
+
Return `True` if pairs are equal, otherwise `False`.
|
|
474
|
+
"""
|
|
475
|
+
return (
|
|
476
|
+
(self.fvec == other.fvec)
|
|
477
|
+
and (self.lvec == other.lvec)
|
|
478
|
+
and (self.sense == other.sense)
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def __ne__(self, other):
|
|
482
|
+
"""
|
|
483
|
+
Return `True` if pairs are not equal, otherwise `False`.
|
|
484
|
+
|
|
485
|
+
"""
|
|
486
|
+
return not self == other
|
|
487
|
+
|
|
488
|
+
def __array__(self, dtype=None, copy=None):
|
|
489
|
+
return np.hstack((self.fvec, self.lvec, self.sense)).astype(dtype)
|
|
490
|
+
|
|
491
|
+
def to_json(self):
|
|
492
|
+
"""Return as JSON dict"""
|
|
493
|
+
fazi, finc = vec2geo_planar_signed(self.fvec)
|
|
494
|
+
lazi, linc = vec2geo_linear_signed(self.lvec)
|
|
495
|
+
return {
|
|
496
|
+
"datatype": type(self).__name__,
|
|
497
|
+
"args": (fazi, finc, lazi, linc, self.sense),
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
@classmethod
|
|
501
|
+
def random(cls):
|
|
502
|
+
"""
|
|
503
|
+
Random Fault
|
|
504
|
+
"""
|
|
505
|
+
import random
|
|
506
|
+
|
|
507
|
+
lvec, p = Vector3.random(), Vector3.random()
|
|
508
|
+
fvec = lvec.cross(p)
|
|
509
|
+
return cls(fvec, lvec, random.choice([-1, 1]))
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def georax(self):
|
|
513
|
+
return self.lvec.lower().cross(self.fvec.lower())
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def sense(self):
|
|
517
|
+
if self.rax == self.georax:
|
|
518
|
+
return 1
|
|
519
|
+
else:
|
|
520
|
+
return -1
|
|
521
|
+
|
|
522
|
+
def p_vector(self, ptangle=90):
|
|
523
|
+
"""Return P axis as ``Vector3``"""
|
|
524
|
+
return self.fvec.rotate(self.lvec.cross(self.fvec), -ptangle / 2)
|
|
525
|
+
|
|
526
|
+
def t_vector(self, ptangle=90):
|
|
527
|
+
"""Return T-axis as ``Vector3``."""
|
|
528
|
+
return self.fvec.rotate(self.lvec.cross(self.fvec), +ptangle / 2)
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def p(self):
|
|
532
|
+
"""Return P-axis as ``Lineation``"""
|
|
533
|
+
return Lineation(self.p_vector())
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def t(self):
|
|
537
|
+
"""Return T-axis as ``Lineation``"""
|
|
538
|
+
return Lineation(self.t_vector())
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def m(self):
|
|
542
|
+
"""Return kinematic M-plane as ``Foliation``"""
|
|
543
|
+
return Foliation(self.lvec.cross(self.fvec))
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def d(self):
|
|
547
|
+
"""Return dihedra plane as ``Fol``"""
|
|
548
|
+
return Foliation(self.lvec.cross(self.fvec).cross(self.fvec))
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
class Cone:
|
|
552
|
+
"""
|
|
553
|
+
The class to store cone with given axis, secant line and revolution angle
|
|
554
|
+
in degrees.
|
|
555
|
+
|
|
556
|
+
There are different way to create ``Cone`` object according to number
|
|
557
|
+
of arguments:
|
|
558
|
+
|
|
559
|
+
- without args, you can create default``Cone`` with axis ``lin(0, 90)``,
|
|
560
|
+
secant ``lin(0, 0)`` angle 360°
|
|
561
|
+
- with single argument `c`, where `c` could be ``Cone``, 5-tuple of
|
|
562
|
+
`(aazi, ainc, sazi, sinc, revangle)` or 7-tuple of
|
|
563
|
+
`(ax, ay ,az, sx, sy, sz, revangle)`
|
|
564
|
+
- with 3 arguments, where axis and secant line could be Vector3 like objects,
|
|
565
|
+
e.g. Lineation and third argument is revolution angle
|
|
566
|
+
- with 5 arguments defining axis `lin(aazi, ainc)`, secant line
|
|
567
|
+
`lin(sazi, sinc)` and angle of revolution
|
|
568
|
+
|
|
569
|
+
Attributes:
|
|
570
|
+
axis (Vector3): axis of the cone
|
|
571
|
+
secant (Vector3): secant line
|
|
572
|
+
revangle (float): revolution angle
|
|
573
|
+
|
|
574
|
+
Example:
|
|
575
|
+
>>> cone()
|
|
576
|
+
>>> cone(c)
|
|
577
|
+
>>> cone(a, s, revangle)
|
|
578
|
+
>>> cone(aazi, ainc, sazi, sinc, revangle)
|
|
579
|
+
>>> c = cone(140, 30, 110, 26, 360)
|
|
580
|
+
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
__slots__ = ("axis", "secant", "revangle")
|
|
584
|
+
__shape__ = (7,)
|
|
585
|
+
|
|
586
|
+
def __init__(self, *args):
|
|
587
|
+
if len(args) == 0:
|
|
588
|
+
axis, secant, revangle = Vector3(0, 0, 1), Vector3(1, 0, 0), 360
|
|
589
|
+
elif len(args) == 1 and issubclass(type(args[0]), Cone):
|
|
590
|
+
axis, secant, revangle = args[0].axis, args[0].secant, args[0].revangle
|
|
591
|
+
elif len(args) == 1 and np.asarray(args[0]).shape == (5,):
|
|
592
|
+
aazi, ainc, sazi, sinc, revangle = (float(v) for v in args[0])
|
|
593
|
+
axis, secant = Lineation(aazi, ainc), Lineation(sazi, sinc)
|
|
594
|
+
elif len(args) == 1 and np.asarray(args[0]).shape == Cone.__shape__:
|
|
595
|
+
axis, secant, revangle = (
|
|
596
|
+
Vector3(args[0][:3]),
|
|
597
|
+
Vector3(args[0][3:6]),
|
|
598
|
+
args[0][-1],
|
|
599
|
+
)
|
|
600
|
+
elif len(args) == 2:
|
|
601
|
+
if issubclass(type(args[0]), Vector3) and issubclass(
|
|
602
|
+
type(args[1]), Vector3
|
|
603
|
+
):
|
|
604
|
+
axis, secant = args
|
|
605
|
+
revangle = 360
|
|
606
|
+
elif issubclass(type(args[0]), Vector3) and np.isscalar(args[1]):
|
|
607
|
+
axis = args[0]
|
|
608
|
+
azi, inc = axis.geo
|
|
609
|
+
secant = Vector3(azi, inc + args[1])
|
|
610
|
+
revangle = 360
|
|
611
|
+
else:
|
|
612
|
+
raise TypeError("Not valid arguments for Cone")
|
|
613
|
+
elif len(args) == 3:
|
|
614
|
+
if issubclass(type(args[0]), Vector3) and issubclass(
|
|
615
|
+
type(args[1]), Vector3
|
|
616
|
+
):
|
|
617
|
+
axis, secant, revangle = args
|
|
618
|
+
else:
|
|
619
|
+
raise TypeError("Not valid arguments for Cone")
|
|
620
|
+
elif len(args) == 4:
|
|
621
|
+
axis = Lineation(args[0], args[1])
|
|
622
|
+
secant = Lineation(args[2], args[3])
|
|
623
|
+
revangle = 360
|
|
624
|
+
elif len(args) == 5:
|
|
625
|
+
axis = Lineation(args[0], args[1])
|
|
626
|
+
secant = Lineation(args[2], args[3])
|
|
627
|
+
revangle = args[4]
|
|
628
|
+
else:
|
|
629
|
+
raise TypeError("Not valid arguments for Cone")
|
|
630
|
+
|
|
631
|
+
self.axis = Vector3(axis)
|
|
632
|
+
self.secant = Vector3(secant)
|
|
633
|
+
self.revangle = float(revangle)
|
|
634
|
+
if self.axis.angle(self.secant) > 90:
|
|
635
|
+
self.secant = -self.secant
|
|
636
|
+
|
|
637
|
+
def __repr__(self):
|
|
638
|
+
azi, inc = vec2geo_linear(self.axis)
|
|
639
|
+
return f"C:{azi:.0f}/{inc:.0f} [{self.apical_angle():g}]"
|
|
640
|
+
|
|
641
|
+
@ensure_first_arg_same
|
|
642
|
+
def __eq__(self, other):
|
|
643
|
+
return (
|
|
644
|
+
(self.axis == other.axis)
|
|
645
|
+
and (self.secant == other.secant)
|
|
646
|
+
and (self.revangle == other.revangle)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def __ne__(self, other):
|
|
650
|
+
return not self == other
|
|
651
|
+
|
|
652
|
+
def __array__(self, dtype=None, copy=None):
|
|
653
|
+
return np.hstack((self.axis, self.secant, self.revangle)).astype(dtype)
|
|
654
|
+
|
|
655
|
+
def label(self):
|
|
656
|
+
"""Return label"""
|
|
657
|
+
return str(self)
|
|
658
|
+
|
|
659
|
+
def to_json(self):
|
|
660
|
+
"""Return as JSON dict"""
|
|
661
|
+
aazi, ainc = vec2geo_linear_signed(self.axis)
|
|
662
|
+
sazi, sinc = vec2geo_linear_signed(self.secant)
|
|
663
|
+
return {
|
|
664
|
+
"datatype": type(self).__name__,
|
|
665
|
+
"args": (aazi, ainc, sazi, sinc, self.revangle),
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
@classmethod
|
|
669
|
+
def random(cls):
|
|
670
|
+
"""
|
|
671
|
+
Random Cone
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
axis, secant = Vector3.random(), Vector3.random()
|
|
675
|
+
return cls(axis, secant, 360)
|
|
676
|
+
|
|
677
|
+
@ensure_arguments(Vector3)
|
|
678
|
+
def rotate(self, axis, phi):
|
|
679
|
+
"""Rotates ``Cone`` by angle `phi` about `axis`.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
axis (``Vector3``): axis of rotation
|
|
683
|
+
phi (float): angle of rotation in degrees
|
|
684
|
+
|
|
685
|
+
Example:
|
|
686
|
+
>>> c = cone(lin(140, 30), lin(110, 26), 360)
|
|
687
|
+
>>> c.rotate(lin(40, 50), 120)
|
|
688
|
+
C:210/83-287/60
|
|
689
|
+
|
|
690
|
+
"""
|
|
691
|
+
return type(self)(
|
|
692
|
+
self.axis.rotate(axis, phi), self.secant.rotate(axis, phi), self.revangle
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
def apical_angle(self):
|
|
696
|
+
"""Return apical angle"""
|
|
697
|
+
return self.axis.angle(self.secant)
|
|
698
|
+
|
|
699
|
+
@property
|
|
700
|
+
def rotated_secant(self):
|
|
701
|
+
"""Return revangle rotated secant vector"""
|
|
702
|
+
return self.secant.rotate(self.axis, self.revangle)
|