code: def project_onto_plane(strike, dip, plunge, bearing):
      
      # Project the line onto the plane
      norm = sph2cart(*pole(strike, dip))
      feature = sph2cart(*line(plunge, bearing))
      norm, feature = np.array(norm), np.array(feature)
      perp = np.cross(norm, feature, axis=0)
      on_plane = np.cross(perp, norm, axis=0)
      on_plane /= np.sqrt(np.sum(on_plane**2, axis=0))
  
      # Calculate the angle between the projected feature and horizontal
      # This is just a dot product, but we need to work with multiple measurements
      # at once, so einsum is quicker than apply_along_axis.
      strike_vec = sph2cart(*line(0, strike))
      dot = np.einsum('ij,ij->j', on_plane, strike_vec)
      rake = np.degrees(np.arccos(dot))
  
      # Convert rakes over 90 to negative rakes...
      rake[rake > 90] -= 180
      rake[rake < -90] += 180
      return rake
docs: Projects a linear feature(s) onto the surface of a plane. Returns a rake
      angle(s) along the plane.
  
      This is also useful for finding the rake angle of a feature that already
      intersects the plane in question.
  
      Parameters
      ----------
      strike, dip : numbers or sequences of numbers
          The strike and dip (in degrees, following the right-hand-rule) of the
          plane(s).
      plunge, bearing : numbers or sequences of numbers
          The plunge and bearing (in degrees) or of the linear feature(s) to be
          projected onto the plane.
  
      Returns
      -------
      rake : array
          A sequence of rake angles measured downwards from horizontal in
          degrees.  Zero degrees corresponds to the "right- hand" direction
          indicated by the strike, while a negative angle corresponds to the
          opposite direction. Rakes returned by this function will always be
          between -90 and 90 (inclusive).