@@ -33,11 +33,12 @@ class ThicknessCalculator(ABC):
3333 ABC (ABC): Derived from Abstract Base Class
3434 """
3535
36- def __init__ (self ):
36+ def __init__ (self , max_line_length : float = None ):
3737 """
3838 Initialiser of for ThicknessCalculator
3939 """
4040 self .thickness_calculator_label = "ThicknessCalculatorBaseClass"
41+ self .max_line_length = max_line_length
4142
4243 def type (self ):
4344 """
@@ -84,7 +85,17 @@ def compute(
8485 )
8586 return units
8687
88+ def _check_thickness_percentage_calculations (self , thicknesses : pandas .DataFrame ):
89+ units_with_no_thickness = len (thicknesses [thicknesses ['ThicknessMean' ] == - 1 ])
90+ total_units = len (thicknesses )
8791
92+ if total_units > 0 and (units_with_no_thickness / total_units ) >= 0.75 :
93+ logger .warning (
94+ f"More than { int (0.75 * 100 )} % of units ({ units_with_no_thickness } /{ total_units } ) "
95+ f"have a calculated thickness of -1. This may indicate that { self .thickness_calculator_label } "
96+ f"is not suitable for this dataset."
97+ )
98+
8899class ThicknessCalculatorAlpha (ThicknessCalculator ):
89100 """
90101 ThicknessCalculator class which estimates unit thickness based on units, basal_contacts and stratigraphic order
@@ -176,6 +187,8 @@ def compute(
176187 val = min (distance , thicknesses .at [idx , "ThicknessMean" ])
177188 thicknesses .loc [idx , "ThicknessMean" ] = val
178189
190+ self ._check_thickness_percentage_calculations (thicknesses )
191+
179192 return thicknesses
180193
181194
@@ -193,12 +206,14 @@ class InterpolatedStructure(ThicknessCalculator):
193206 -> pandas.DataFrame: Calculates a thickness map for the overall map area.
194207 """
195208
196- def __init__ (self ):
209+ def __init__ (self , max_line_length : float = None ):
197210 """
198211 Initialiser for interpolated structure version of the thickness calculator
199212 """
213+ super ().__init__ (max_line_length )
200214 self .thickness_calculator_label = "InterpolatedStructure"
201- self .lines = []
215+ self .lines = None
216+
202217
203218 @beartype .beartype
204219 def compute (
@@ -297,7 +312,11 @@ def compute(
297312 interpolated_orientations = interpolated_orientations [
298313 ["geometry" , "dip" , "UNITNAME" ]
299314 ].copy ()
300-
315+
316+ _lines = []
317+ _dips = []
318+ _location_tracking = []
319+
301320 for i in range (0 , len (stratigraphic_order ) - 1 ):
302321 if (
303322 stratigraphic_order [i ] in basal_unit_list
@@ -319,11 +338,18 @@ def compute(
319338 dip = interpolated_orientations .loc [
320339 interpolated_orientations ["UNITNAME" ] == stratigraphic_order [i ], "dip"
321340 ].to_numpy ()
341+
322342 _thickness = []
343+
323344 for _ , row in basal_contact .iterrows ():
324345 # find the shortest line between the basal contact points and top contact points
325346 short_line = shapely .shortest_line (row .geometry , top_contact_geometry )
326- self .lines .append (short_line )
347+ _lines .append (short_line [0 ])
348+
349+ # check if the short line is
350+ if self .max_line_length is not None and short_line .length > self .max_line_length :
351+ continue
352+
327353 # extract the end points of the shortest line
328354 p1 = numpy .zeros (3 )
329355 p1 [0 ] = numpy .asarray (short_line [0 ].coords [0 ][0 ])
@@ -336,18 +362,27 @@ def compute(
336362 p2 [1 ] = numpy .asarray (short_line [0 ].coords [- 1 ][1 ])
337363 # get the elevation Z of the end point p2
338364 p2 [2 ] = map_data .get_value_from_raster (Datatype .DTM , p2 [0 ], p2 [1 ])
339- # get the elevation Z of the end point p2
340- p2 [2 ] = map_data .get_value_from_raster (Datatype .DTM , p2 [0 ], p2 [1 ])
341365 # calculate the length of the shortest line
342366 line_length = scipy .spatial .distance .euclidean (p1 , p2 )
343367 # find the indices of the points that are within 5% of the length of the shortest line
344368 indices = shapely .dwithin (short_line , interp_points , line_length * 0.25 )
345369 # get the dip of the points that are within
346- # 10% of the length of the shortest line
347370 _dip = numpy .deg2rad (dip [indices ])
348- # get the end points of the shortest line
349- # calculate the true thickness t = L . sin dip
371+ _dips . append ( _dip )
372+ # calculate the true thickness t = L * sin( dip)
350373 thickness = line_length * numpy .sin (_dip )
374+
375+ # add location tracking
376+ location_tracking = pandas .DataFrame (
377+ {
378+ "p1_x" : [p1 [0 ]], "p1_y" : [p1 [1 ]], "p1_z" : [p1 [2 ]],
379+ "p2_x" : [p2 [0 ]], "p2_y" : [p2 [1 ]], "p2_z" : [p2 [2 ]],
380+ "thickness" : [thickness ],
381+ "unit" : [stratigraphic_order [i ]]
382+ }
383+ )
384+ _location_tracking .append (location_tracking )
385+
351386 # Average thickness along the shortest line
352387 if all (numpy .isnan (thickness )):
353388 pass
@@ -372,10 +407,22 @@ def compute(
372407 logger .warning (
373408 f"Thickness Calculator InterpolatedStructure: Cannot calculate thickness between { stratigraphic_order [i ]} and { stratigraphic_order [i + 1 ]} \n "
374409 )
375-
410+
411+ # Combine all location_tracking DataFrames into a single DataFrame
412+ combined_location_tracking = pandas .concat (_location_tracking , ignore_index = True )
413+
414+ # Save the combined DataFrame as an attribute of the class
415+ self .location_tracking = combined_location_tracking
416+
417+ # Create GeoDataFrame for lines
418+ self .lines = geopandas .GeoDataFrame (geometry = _lines , crs = basal_contacts .crs )
419+ self .lines ['dip' ] = _dips
420+
421+ # Check thickness calculation
422+ self ._check_thickness_percentage_calculations (thicknesses )
423+
376424 return thicknesses
377425
378-
379426class StructuralPoint (ThicknessCalculator ):
380427 '''
381428 This class is a subclass of the ThicknessCalculator abstract base class. It implements the thickness calculation using a deterministic workflow based on stratigraphic measurements.
@@ -390,10 +437,12 @@ class StructuralPoint(ThicknessCalculator):
390437
391438 '''
392439
393- def __init__ (self ):
440+ def __init__ (self , max_line_length : float = None ):
441+ super ().__init__ (max_line_length )
394442 self .thickness_calculator_label = "StructuralPoint"
395- self .line_length = 10000
396443 self .strike_allowance = 30
444+ self .lines = None
445+
397446
398447 @beartype .beartype
399448 def compute (
@@ -474,6 +523,8 @@ def compute(
474523 # create empty lists to store thicknesses and lithologies
475524 thicknesses = []
476525 lis = []
526+ _lines = []
527+ _dip = []
477528
478529 # loop over each sampled structural measurement
479530 for s in range (0 , len (sampled_structures )):
@@ -505,7 +556,9 @@ def compute(
505556 )
506557
507558 # draw orthogonal line to the strike (default value 10Km), and clip it by the bounding box of the lithology
508- B = calculate_endpoints (measurement_pt , strike , self .line_length , bbox_poly )
559+ if self .max_line_length is None :
560+ self .max_line_length = 10000
561+ B = calculate_endpoints (measurement_pt , strike , self .max_line_length , bbox_poly )
509562 b = geopandas .GeoDataFrame ({'geometry' : [B ]}).set_crs (basal_contacts .crs )
510563
511564 # find all intersections
@@ -577,15 +630,28 @@ def compute(
577630 if not (b_s [0 ] < strike1 < b_s [1 ] and b_s [0 ] < strike2 < b_s [1 ]):
578631 continue
579632
633+ #build the debug info
634+ line = shapely .geometry .LineString ([int_pt1 , int_pt2 ])
635+ _lines .append (line )
636+ _dip .append (measurement ['DIP' ])
637+
580638 # find the lenght of the segment
581639 L = math .sqrt (((int_pt1 .x - int_pt2 .x ) ** 2 ) + ((int_pt1 .y - int_pt2 .y ) ** 2 ))
582640
641+ # if length is higher than max_line_length, skip
642+ if self .max_line_length is not None and L > self .max_line_length :
643+ continue
644+
583645 # calculate thickness
584646 thickness = L * math .sin (math .radians (measurement ['DIP' ]))
585647
586648 thicknesses .append (thickness )
587649 lis .append (litho_in )
588-
650+
651+ # create the debug gdf
652+ self .lines = geopandas .GeoDataFrame (geometry = _lines , crs = basal_contacts .crs )
653+ self .lines ["DIP" ] = _dip
654+
589655 # create a DataFrame of the thicknesses median and standard deviation by lithology
590656 result = pandas .DataFrame ({'unit' : lis , 'thickness' : thicknesses })
591657 result = result .groupby ('unit' )['thickness' ].agg (['median' , 'mean' , 'std' ]).reset_index ()
@@ -640,4 +706,6 @@ def compute(
640706 output_units .loc [output_units ["name" ] == unit , "ThicknessMean" ] = - 1
641707 output_units .loc [output_units ["name" ] == unit , "ThicknessStdDev" ] = - 1
642708
709+ self ._check_thickness_percentage_calculations (output_units )
710+
643711 return output_units
0 commit comments