@@ -295,29 +295,69 @@ def create_data_from_geometry(
295295 self .fault_dip = fault_dip
296296 if fault_normal_vector is None :
297297 if fault_frame_data .loc [
298+ np .logical_and (fault_frame_data ["coord" ] == 0 , fault_frame_data ["gx" ].notna ())].shape [0 ]> 0 :
299+ fault_normal_vector = fault_frame_data .loc [
300+ np .logical_and (fault_frame_data ["coord" ] == 0 , fault_frame_data ["gx" ].notna ()),
301+ ["gx" , "gy" , "gz" ],
302+ ].to_numpy ().mean (axis = 0 )
303+ elif fault_frame_data .loc [
298304 np .logical_and (fault_frame_data ["coord" ] == 0 , fault_frame_data ["nx" ].notna ())].shape [0 ]> 0 :
299305 fault_normal_vector = fault_frame_data .loc [
300306 np .logical_and (fault_frame_data ["coord" ] == 0 , fault_frame_data ["nx" ].notna ()),
301307 ["nx" , "ny" , "nz" ],
302308 ].to_numpy ().mean (axis = 0 )
303309
304310 else :
311+ fault_surface_pts = fault_frame_data .loc [
312+ np .logical_and (
313+ fault_frame_data ["coord" ] == 0 ,
314+ fault_frame_data ["val" ] == 0 ,
315+ ),
316+ ["X" , "Y" , "Z" ],
317+ ].to_numpy ()
318+ fault_surface_pts = fault_surface_pts [
319+ ~ np .isnan (fault_surface_pts ).any (axis = 1 )
320+ ]
305321
306- # Calculate fault strike using eigenvectors
307- pts = fault_trace - fault_trace .mean (axis = 0 )
308- # Calculate covariance matrix
309- cov_matrix = pts .T @ pts
310- # Get eigenvectors and eigenvalues
311- eigenvalues , eigenvectors = np .linalg .eigh (cov_matrix )
312- # Use eigenvector with largest eigenvalue as strike direction
313- strike_vector = eigenvectors [:, np .argmax (eigenvalues )]
314- strike_vector = np .append (strike_vector , 0 ) # Add z component
315- strike_vector /= np .linalg .norm (strike_vector )
316-
317- fault_normal_vector = np .cross (strike_vector , [0 , 0 , 1 ])
318- # Rotate the fault normal vector according to the fault dip
319- rotation_matrix = rotation (strike_vector [None , :], np .array ([90 - fault_dip ]))
320- fault_normal_vector = np .einsum ("ijk,ik->ij" , rotation_matrix , fault_normal_vector [None , :])[0 ]
322+ if fault_surface_pts .shape [0 ] >= 3 :
323+ pts_3d = fault_surface_pts - fault_surface_pts .mean (axis = 0 )
324+ cov_matrix = pts_3d .T @ pts_3d
325+ eigenvalues , eigenvectors = np .linalg .eigh (cov_matrix )
326+ # If points span a surface, use the smallest eigenvector as plane normal
327+ if eigenvalues [- 1 ] > 0 and (eigenvalues [1 ] / eigenvalues [- 1 ]) > 0.05 :
328+ fault_normal_vector = eigenvectors [:, 0 ]
329+ else :
330+ # Fall back to line-on-map strike logic
331+ pts = fault_trace - fault_trace .mean (axis = 0 )
332+ cov_matrix = pts .T @ pts
333+ eigenvalues , eigenvectors = np .linalg .eigh (cov_matrix )
334+ strike_vector = eigenvectors [:, np .argmax (eigenvalues )]
335+ strike_vector = np .append (strike_vector , 0 ) # Add z component
336+ strike_vector /= np .linalg .norm (strike_vector )
337+
338+ fault_normal_vector = np .cross (strike_vector , [0 , 0 , 1 ])
339+ rotation_matrix = rotation (
340+ strike_vector [None , :], np .array ([90 - fault_dip ])
341+ )
342+ fault_normal_vector = np .einsum (
343+ "ijk,ik->ij" , rotation_matrix , fault_normal_vector [None , :]
344+ )[0 ]
345+ else :
346+ # Fall back to line-on-map strike logic
347+ pts = fault_trace - fault_trace .mean (axis = 0 )
348+ cov_matrix = pts .T @ pts
349+ eigenvalues , eigenvectors = np .linalg .eigh (cov_matrix )
350+ strike_vector = eigenvectors [:, np .argmax (eigenvalues )]
351+ strike_vector = np .append (strike_vector , 0 ) # Add z component
352+ strike_vector /= np .linalg .norm (strike_vector )
353+
354+ fault_normal_vector = np .cross (strike_vector , [0 , 0 , 1 ])
355+ rotation_matrix = rotation (
356+ strike_vector [None , :], np .array ([90 - fault_dip ])
357+ )
358+ fault_normal_vector = np .einsum (
359+ "ijk,ik->ij" , rotation_matrix , fault_normal_vector [None , :]
360+ )[0 ]
321361
322362 if not isinstance (fault_normal_vector , np .ndarray ):
323363 fault_normal_vector = np .array (fault_normal_vector )
0 commit comments