docs: def from_fault_data(cls, fault_trace, upper_seismogenic_depth,
                          lower_seismogenic_depth, dip, mesh_spacing):
          """
          Create and return a fault surface using fault source data.
  
          :param openquake.hazardlib.geo.line.Line fault_trace:
              Geographical line representing the intersection between the fault
              surface and the earth surface. The line must be horizontal (i.e.
              all depth values must be equal). If the depths are not given, they
              are assumed to be zero, meaning the trace intersects the surface at
              sea level, e.g. fault_trace = Line([Point(1, 1), Point(1, 2)]).
          :param upper_seismo_depth:
              Minimum depth ruptures can reach, in km (i.e. depth
              to fault's top edge).
          :param lower_seismo_depth:
              Maximum depth ruptures can reach, in km (i.e. depth
              to fault's bottom edge).
          :param dip:
              Dip angle (i.e. angle between fault surface
              and earth surface), in degrees.
          :param mesh_spacing:
              Distance between two subsequent points in a mesh, in km.
          :returns:
              An instance of :class:`SimpleFaultSurface` created using that data.
  
          Uses :meth:`check_fault_data` for checking parameters.
          """
          cls.check_fault_data(fault_trace, upper_seismogenic_depth,
                               lower_seismogenic_depth, dip, mesh_spacing)
          # Loops over points in the top edge, for each point
          # on the top edge compute corresponding point on the bottom edge, then
          # computes equally spaced points between top and bottom points.
  
          vdist_top = upper_seismogenic_depth - fault_trace[0].depth
          vdist_bottom = lower_seismogenic_depth - fault_trace[0].depth
  
          hdist_top = vdist_top / math.tan(math.radians(dip))
          hdist_bottom = vdist_bottom / math.tan(math.radians(dip))
  
          strike = fault_trace[0].azimuth(fault_trace[-1])
          azimuth = (strike + 90.0) % 360
  
          mesh = []
          for point in fault_trace.resample(mesh_spacing):
              top = point.point_at(hdist_top, vdist_top, azimuth)
              bottom = point.point_at(hdist_bottom, vdist_bottom, azimuth)
              mesh.append(top.equally_spaced_points(bottom, mesh_spacing))
  
          # number of rows corresponds to number of points along dip
          # number of columns corresponds to number of points along strike
          surface_points = numpy.array(mesh).transpose().tolist()
          mesh = RectangularMesh.from_points_list(surface_points)
          assert 1 not in mesh.shape, (
              "Mesh must have at least 2 nodes along both length and width."
              " Possible cause: Mesh spacing could be too large with respect to"
              " the fault length and width."
          )
          self = cls(mesh)
          self.surface_nodes = [simple_fault_node(
              fault_trace, dip,
              upper_seismogenic_depth, lower_seismogenic_depth)]
          return self