diff --git a/src/sampling/Make/files b/src/sampling/Make/files
index f7d9f7d4b490c37da7dc6ae0e3dd02ba12c13d86..22c32f0eeb11f19d3686dc45e44ecb79d113178e 100644
--- a/src/sampling/Make/files
+++ b/src/sampling/Make/files
@@ -28,6 +28,7 @@ surface/cutting/cuttingSurfaceCuts.C
 surface/cutting/cuttingSurfaceBase.C
 surface/cutting/cuttingSurfaceBaseSelection.C
 surface/distanceSurface/distanceSurface.C
+surface/distanceSurface/distanceSurfaceFilter.C
 surface/isoSurface/isoSurfaceBase.C
 surface/isoSurface/isoSurfaceBaseNew.C
 surface/isoSurface/isoSurfaceParams.C
diff --git a/src/sampling/sampledSurface/distanceSurface/sampledDistanceSurface.H b/src/sampling/sampledSurface/distanceSurface/sampledDistanceSurface.H
index e3d5df2509e4e0eb9294a4fc79f72c9ee756801a..ce208cb2591ea72c4c7944c8def26bbd69949ee7 100644
--- a/src/sampling/sampledSurface/distanceSurface/sampledDistanceSurface.H
+++ b/src/sampling/sampledSurface/distanceSurface/sampledDistanceSurface.H
@@ -40,6 +40,9 @@ Usage
         surface1
         {
             type        distanceSurface;
+            surfaceType triSurfaceMesh;
+            surfaceName something.obj;
+            topology    proximity;
         }
     }
     \endverbatim
@@ -48,18 +51,23 @@ Usage
     \table
         Property | Description                              | Required | Default
         type     | distanceSurface                          | yes |
-        distance | Distance from surface                    | yes |
-        signed   | Use sign when distance is positive       | partly |
-        isoMethod | Iso-algorithm (cell/topo/point)         | no  | topo
-        regularise | Point snapping for iso-surface         | no  | true
+        distance | distance from surface                    | no  | 0
+        signed   | Use sign when distance is positive       | no  | true
+        isoMethod | Iso-algorithm (cell/topo/point)         | no  | default
+        regularise | Face simplification (enum or bool)     | no  | true
         average  | Cell values from averaged point values   | no  | false
         bounds   | Limit with bounding box                  | no  |
         surfaceType | Type of surface                       | yes |
         surfaceName | Name of surface in \c triSurface/     | no  | dict name
+        topology    | Topology filter (none/largestRegion/nearestPoints/proximity) | no  | none
+        nearestPoints | Points for point-based segmentation | no  |
+        maxDistance | Max search distance for nearestPoints | no  | GREAT
+        relProximity | Max limit of absolute vs normal distance | no  | 1e3
     \endtable
 
 SourceFiles
     sampledDistanceSurface.C
+    sampledDistanceSurfaceTemplates.C
 
 \*---------------------------------------------------------------------------*/
 
diff --git a/src/sampling/surface/distanceSurface/distanceSurface.C b/src/sampling/surface/distanceSurface/distanceSurface.C
index 0c6f4423ff37f7740da8c04f8b689fe19f5069d7..be57d3b122b6aa294f6f73a6bc65c580d2f07265 100644
--- a/src/sampling/surface/distanceSurface/distanceSurface.C
+++ b/src/sampling/surface/distanceSurface/distanceSurface.C
@@ -41,6 +41,205 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+const Foam::Enum
+<
+    Foam::distanceSurface::topologyFilterType
+>
+Foam::distanceSurface::topoFilterNames_
+({
+    { topologyFilterType::NONE, "none" },
+    { topologyFilterType::LARGEST_REGION, "largestRegion" },
+    { topologyFilterType::NEAREST_POINTS, "nearestPoints" },
+    { topologyFilterType::PROXIMITY, "proximity" },
+});
+
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Check that all point hits are valid
+static inline void checkAllHits(const UList<pointIndexHit>& nearest)
+{
+    label notHit = 0;
+    for (const pointIndexHit& pHit : nearest)
+    {
+        if (!pHit.hit())
+        {
+            ++notHit;
+        }
+    }
+
+    if (notHit)
+    {
+        FatalErrorInFunction
+            << "Had " << notHit << " from " << nearest.size()
+            << " without a point hit" << endl
+            << abort(FatalError);
+    }
+}
+
+
+// Normal distance from surface hit point to a point in the mesh
+static inline scalar normalDistance_zero
+(
+    const point& pt,
+    const pointIndexHit& pHit,
+    const vector& norm
+)
+{
+    const vector diff(pt - pHit.point());
+
+    return (diff & norm);
+}
+
+
+// Signed distance from surface hit point to a point in the mesh,
+// the sign is dictated by the normal
+static inline scalar normalDistance_nonzero
+(
+    const point& pt,
+    const pointIndexHit& pHit,
+    const vector& norm
+)
+{
+    const vector diff(pt - pHit.point());
+    const scalar normDist = (diff & norm);
+
+    return Foam::sign(normDist) * Foam::mag(diff);
+}
+
+
+// Normal distance from surface hit point to a point in the mesh
+static inline void calcNormalDistance_zero
+(
+    scalarField& distance,
+    const pointField& points,
+    const List<pointIndexHit>& nearest,
+    const vectorField& normals
+)
+{
+    forAll(nearest, i)
+    {
+        distance[i] =
+            normalDistance_zero(points[i], nearest[i], normals[i]);
+    }
+}
+
+
+// Signed distance from surface hit point -> point in the mesh,
+// the sign is dictated by the normal
+static inline void calcNormalDistance_nonzero
+(
+    scalarField& distance,
+    const pointField& points,
+    const List<pointIndexHit>& nearest,
+    const vectorField& normals
+)
+{
+    forAll(nearest, i)
+    {
+        distance[i] =
+            normalDistance_nonzero(points[i], nearest[i], normals[i]);
+    }
+}
+
+
+// Close to the surface: normal distance from surface hit point
+// Far from surface: distance from surface hit point
+//
+// Note
+// This switch may be helpful when working directly with
+// distance/gradient fields. Has low overhead otherwise.
+// May be replaced in the future (2020-11)
+static inline void calcNormalDistance_filtered
+(
+    scalarField& distance,
+    const bitSet& ignoreLocation,
+    const pointField& points,
+    const List<pointIndexHit>& nearest,
+    const vectorField& normals
+)
+{
+    forAll(nearest, i)
+    {
+        if (ignoreLocation.test(i))
+        {
+            distance[i] =
+                normalDistance_nonzero(points[i], nearest[i], normals[i]);
+        }
+        else
+        {
+            distance[i] =
+                normalDistance_zero(points[i], nearest[i], normals[i]);
+        }
+    }
+}
+
+
+// Flat surfaces (eg, a plane) have an extreme change in
+// the normal at the edge, which creates a zero-crossing
+// extending to infinity.
+//
+// Ad hoc treatment: require that the surface hit
+// point is within a somewhat generous bounding box
+// for the cell
+template<bool WantPointFilter = false>
+static bitSet simpleGeometricFilter
+(
+    bitSet& ignoreCells,
+    const List<pointIndexHit>& nearest,
+    const polyMesh& mesh
+)
+{
+    // A deny filter. Initially false (accept everything)
+    ignoreCells.resize(mesh.nCells());
+
+    bitSet pointFilter;
+    if (WantPointFilter)
+    {
+        // Create as accept filter. Initially false (deny everything)
+        pointFilter.resize(mesh.nPoints());
+    }
+
+    boundBox cellBb;
+
+    forAll(nearest, celli)
+    {
+        const point& pt = nearest[celli].point();
+
+        const labelList& cPoints = mesh.cellPoints(celli);
+
+        cellBb.clear();
+        cellBb.add(mesh.points(), cPoints);
+
+        // Expand slightly to catch corners
+        cellBb.inflate(0.1);
+
+        if (!cellBb.contains(pt))
+        {
+            ignoreCells.set(celli);
+        }
+        else if (WantPointFilter)
+        {
+            // Good cell candidate, accept its points
+            pointFilter.set(cPoints);
+        }
+    }
+
+    // Flip from accept to deny filter
+    pointFilter.flip();
+
+    return pointFilter;
+}
+
+
+} // End namespace Foam
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::distanceSurface::distanceSurface
@@ -51,7 +250,7 @@ Foam::distanceSurface::distanceSurface
 )
 :
     mesh_(mesh),
-    surfPtr_
+    geometryPtr_
     (
         searchableSurface::New
         (
@@ -68,10 +267,13 @@ Foam::distanceSurface::distanceSurface
             dict
         )
     ),
-    distance_(dict.get<scalar>("distance")),
-    signed_
+    distance_(dict.getOrDefault<scalar>("distance", 0)),
+    withZeroDistance_(equal(distance_, 0)),
+    withSignDistance_
     (
-        distance_ < 0 || equal(distance_, Zero) || dict.get<bool>("signed")
+        withZeroDistance_
+     || (distance_ < 0)
+     || dict.getOrDefault<bool>("signed", true)
     ),
     isoParams_
     (
@@ -79,9 +281,55 @@ Foam::distanceSurface::distanceSurface
         isoSurfaceParams::ALGO_TOPO,
         isoSurfaceParams::filterType::DIAGCELL
     ),
+    topoFilter_
+    (
+        topoFilterNames_.getOrDefault
+        (
+            "topology",
+            dict,
+            topologyFilterType::NONE
+        )
+    ),
+    nearestPoints_(),
+    maxDistanceSqr_(Foam::sqr(GREAT)),
+    absProximity_(dict.getOrDefault<scalar>("absProximity", 1e-5)),
+    cellDistancePtr_(nullptr),
+    pointDistance_(),
     surface_(),
     meshCells_(),
     isoSurfacePtr_(nullptr)
+{
+    if (topologyFilterType::NEAREST_POINTS == topoFilter_)
+    {
+        dict.readEntry("nearestPoints", nearestPoints_);
+    }
+
+    if (dict.readIfPresent("maxDistance", maxDistanceSqr_))
+    {
+        maxDistanceSqr_ = Foam::sqr(maxDistanceSqr_);
+    }
+}
+
+
+Foam::distanceSurface::distanceSurface
+(
+    const polyMesh& mesh,
+    const word& surfaceType,
+    const word& surfaceName,
+    const isoSurfaceParams& params,
+    const bool interpolate
+)
+:
+    distanceSurface
+    (
+        mesh,
+        interpolate,
+        surfaceType,
+        surfaceName,
+        scalar(0),
+        true, // redundant - must be signed
+        params
+    )
 {}
 
 
@@ -97,7 +345,7 @@ Foam::distanceSurface::distanceSurface
 )
 :
     mesh_(mesh),
-    surfPtr_
+    geometryPtr_
     (
         searchableSurface::New
         (
@@ -115,11 +363,20 @@ Foam::distanceSurface::distanceSurface
         )
     ),
     distance_(distance),
-    signed_
+    withZeroDistance_(equal(distance_, 0)),
+    withSignDistance_
     (
-        useSignedDistance || distance_ < 0 || equal(distance_, Zero)
+        withZeroDistance_
+     || (distance_ < 0)
+     || useSignedDistance
     ),
     isoParams_(params),
+    topoFilter_(topologyFilterType::NONE),
+    nearestPoints_(),
+    maxDistanceSqr_(Foam::sqr(GREAT)),
+    absProximity_(1e-5),
+    cellDistancePtr_(nullptr),
+    pointDistance_(),
     surface_(),
     meshCells_(),
     isoSurfacePtr_(nullptr)
@@ -140,7 +397,10 @@ void Foam::distanceSurface::createGeometry()
     surface_.clear();
     meshCells_.clear();
 
-    const fvMesh& fvm = static_cast<const fvMesh&>(mesh_);
+    // Doing searches on this surface
+    const searchableSurface& geom = geometryPtr_();
+
+    const fvMesh& fvmesh = static_cast<const fvMesh&>(mesh_);
 
     // Distance to cell centres
     // ~~~~~~~~~~~~~~~~~~~~~~~~
@@ -152,132 +412,126 @@ void Foam::distanceSurface::createGeometry()
             IOobject
             (
                 "distanceSurface.cellDistance",
-                fvm.time().timeName(),
-                fvm.time(),
+                fvmesh.time().timeName(),
+                fvmesh.time(),
                 IOobject::NO_READ,
                 IOobject::NO_WRITE,
                 false
             ),
-            fvm,
-            dimensionedScalar(dimLength, Zero)
+            fvmesh,
+            dimensionedScalar(dimLength, GREAT)
         )
     );
-    volScalarField& cellDistance = *cellDistancePtr_;
+    auto& cellDistance = *cellDistancePtr_;
 
-    // For distance = 0 (and isoSurfaceCell) we apply additional filtering
+
+    // For distance = 0 we apply additional geometric filtering
     // to limit the extent of open edges.
+    //
+    // Does not work with ALGO_POINT
+
+    bitSet ignoreCells, ignoreCellPoints;
 
-    const bool isZeroDist  = equal(distance_, Zero);
     const bool filterCells =
     (
-        isZeroDist
+        withZeroDistance_
      && isoParams_.algorithm() != isoSurfaceParams::ALGO_POINT
     );
 
-    bitSet ignoreCells;
-    if (filterCells)
-    {
-        ignoreCells.resize(fvm.nCells());
-    }
 
     // Internal field
     {
-        const pointField& cc = fvm.C();
+        const pointField& cc = fvmesh.C();
         scalarField& fld = cellDistance.primitiveFieldRef();
 
         List<pointIndexHit> nearest;
-        surfPtr_().findNearest
+        geom.findNearest
         (
             cc,
-            scalarField(cc.size(), GREAT),
+            // Use initialized field (GREAT) to limit search too
+            fld,
             nearest
         );
+        checkAllHits(nearest);
 
-        if (signed_ || isZeroDist)
+        // Geometric pre-filtering when distance == 0
+        if (filterCells)
         {
-            vectorField norms;
-            surfPtr_().getNormal(nearest, norms);
+            ignoreCellPoints =
+                simpleGeometricFilter<false>(ignoreCells, nearest, fvmesh);
+        }
 
-            boundBox cellBb;
+        if (withSignDistance_)
+        {
+            vectorField norms;
+            geom.getNormal(nearest, norms);
 
-            forAll(norms, i)
+            if (filterCells)
             {
-                const point diff(cc[i] - nearest[i].hitPoint());
-
-                fld[i] =
+                // With inside/outside switching (see note above)
+                calcNormalDistance_filtered
                 (
-                    isZeroDist // Use normal distance
-                  ? (diff & norms[i])
-                  : Foam::sign(diff & norms[i]) * Foam::mag(diff)
+                    fld,
+                    ignoreCells,
+                    cc,
+                    nearest,
+                    norms
                 );
-
-                if (filterCells)
-                {
-                    cellBb.clear();
-                    cellBb.add(fvm.points(), fvm.cellPoints(i));
-
-                    // Expand slightly to catch corners
-                    cellBb.inflate(0.1);
-
-                    if (!cellBb.contains(nearest[i].hitPoint()))
-                    {
-                        ignoreCells.set(i);
-                    }
-                }
+            }
+            else if (withZeroDistance_)
+            {
+                calcNormalDistance_zero(fld, cc, nearest, norms);
+            }
+            else
+            {
+                calcNormalDistance_nonzero(fld, cc, nearest, norms);
             }
         }
         else
         {
-            forAll(nearest, i)
-            {
-                fld[i] = Foam::mag(cc[i] - nearest[i].hitPoint());
-
-                // No filtering for unsigned or distance != 0.
-            }
+            calcAbsoluteDistance(fld, cc, nearest);
         }
     }
 
+
     // Patch fields
     {
-        volScalarField::Boundary& cellDistanceBf =
-            cellDistance.boundaryFieldRef();
-
-        forAll(fvm.C().boundaryField(), patchi)
+        forAll(fvmesh.C().boundaryField(), patchi)
         {
-            const pointField& cc = fvm.C().boundaryField()[patchi];
-            fvPatchScalarField& fld = cellDistanceBf[patchi];
+            const pointField& cc = fvmesh.C().boundaryField()[patchi];
+            scalarField& fld = cellDistance.boundaryFieldRef()[patchi];
 
             List<pointIndexHit> nearest;
-            surfPtr_().findNearest
+            geom.findNearest
             (
                 cc,
                 scalarField(cc.size(), GREAT),
                 nearest
             );
+            checkAllHits(nearest);
 
-            if (signed_)
+            if (withSignDistance_)
             {
                 vectorField norms;
-                surfPtr_().getNormal(nearest, norms);
+                geom.getNormal(nearest, norms);
+
+                if (withZeroDistance_)
+                {
+                    // Slight inconsistency in boundary vs interior when
+                    // cells are filtered, but the patch fields are only
+                    // used by isoSurfacePoint, and filtering is disabled
+                    // for that anyhow.
 
-                forAll(norms, i)
+                    calcNormalDistance_zero(fld, cc, nearest, norms);
+                }
+                else
                 {
-                    const point diff(cc[i] - nearest[i].hitPoint());
-
-                    fld[i] =
-                    (
-                        isZeroDist // Use normal distance
-                      ? (diff & norms[i])
-                      : Foam::sign(diff & norms[i]) * Foam::mag(diff)
-                    );
+                    calcNormalDistance_nonzero(fld, cc, nearest, norms);
                 }
             }
             else
             {
-                forAll(nearest, i)
-                {
-                    fld[i] = Foam::mag(cc[i] - nearest[i].hitPoint());
-                }
+                calcAbsoluteDistance(fld, cc, nearest);
             }
         }
     }
@@ -288,44 +542,86 @@ void Foam::distanceSurface::createGeometry()
 
 
     // Distance to points
-    pointDistance_.resize(fvm.nPoints());
+    pointDistance_.resize(fvmesh.nPoints());
+    pointDistance_ = GREAT;
     {
-        const pointField& pts = fvm.points();
+        const pointField& pts = fvmesh.points();
+        scalarField& fld = pointDistance_;
 
         List<pointIndexHit> nearest;
-        surfPtr_().findNearest
+        geom.findNearest
         (
             pts,
-            scalarField(pts.size(), GREAT),
+            // Use initialized field (GREAT) to limit search too
+            pointDistance_,
             nearest
         );
+        checkAllHits(nearest);
 
-        if (signed_)
+        if (withSignDistance_)
         {
             vectorField norms;
-            surfPtr_().getNormal(nearest, norms);
+            geom.getNormal(nearest, norms);
 
-            forAll(norms, i)
+            if (filterCells)
             {
-                const point diff(pts[i] - nearest[i].hitPoint());
-
-                pointDistance_[i] =
+                // With inside/outside switching (see note above)
+                calcNormalDistance_filtered
                 (
-                    isZeroDist // Use normal distance
-                  ? (diff & norms[i])
-                  : Foam::sign(diff & norms[i]) * Foam::mag(diff)
+                    fld,
+                    ignoreCellPoints,
+                    pts,
+                    nearest,
+                    norms
                 );
             }
+            else if (withZeroDistance_)
+            {
+                calcNormalDistance_zero(fld, pts, nearest, norms);
+            }
+            else
+            {
+                calcNormalDistance_nonzero(fld, pts, nearest, norms);
+            }
         }
         else
         {
-            forAll(nearest, i)
-            {
-                pointDistance_[i] = Foam::mag(pts[i] - nearest[i].hitPoint());
-            }
+            calcAbsoluteDistance(fld, pts, nearest);
+        }
+    }
+
+
+    // Don't need ignoreCells if there is nothing to ignore.
+    if (ignoreCells.none())
+    {
+        ignoreCells.clearStorage();
+    }
+    else if (filterCells && topologyFilterType::NONE != topoFilter_)
+    {
+        // For refine blocked cells (eg, checking actual cells cut)
+        isoSurfaceBase isoCutter
+        (
+            mesh_,
+            cellDistance,
+            pointDistance_,
+            distance_
+        );
+
+        if (topologyFilterType::LARGEST_REGION == topoFilter_)
+        {
+            refineBlockedCells(ignoreCells, isoCutter);
+            filterKeepLargestRegion(ignoreCells);
+        }
+        else if (topologyFilterType::NEAREST_POINTS == topoFilter_)
+        {
+            refineBlockedCells(ignoreCells, isoCutter);
+            filterKeepNearestRegions(ignoreCells);
         }
     }
 
+    // Don't need point filter beyond this point
+    ignoreCellPoints.clearStorage();
+
 
     if (debug)
     {
@@ -336,13 +632,13 @@ void Foam::distanceSurface::createGeometry()
             IOobject
             (
                 "distanceSurface.pointDistance",
-                fvm.time().timeName(),
-                fvm.time(),
+                fvmesh.time().timeName(),
+                fvmesh.time(),
                 IOobject::NO_READ,
                 IOobject::NO_WRITE,
                 false
             ),
-            pointMesh::New(fvm),
+            pointMesh::New(fvmesh),
             dimensionedScalar(dimLength, Zero)
         );
         pDist.primitiveFieldRef() = pointDistance_;
@@ -351,13 +647,6 @@ void Foam::distanceSurface::createGeometry()
         pDist.write();
     }
 
-    // Don't need ignoreCells if there is nothing to ignore.
-    if (!ignoreCells.any())
-    {
-        ignoreCells.clear();
-    }
-
-
     isoSurfacePtr_.reset
     (
         isoSurfaceBase::New
@@ -370,9 +659,16 @@ void Foam::distanceSurface::createGeometry()
         )
     );
 
+
     // ALGO_POINT still needs cell, point fields (for interpolate)
     // The others can do straight transfer
-    if (isoParams_.algorithm() != isoSurfaceParams::ALGO_POINT)
+
+    // But also flatten into a straight transfer for proximity filtering
+    if
+    (
+        isoParams_.algorithm() != isoSurfaceParams::ALGO_POINT
+     || topologyFilterType::PROXIMITY == topoFilter_
+    )
     {
         surface_.transfer(static_cast<meshedSurface&>(*isoSurfacePtr_));
         meshCells_.transfer(isoSurfacePtr_->meshCells());
@@ -382,6 +678,11 @@ void Foam::distanceSurface::createGeometry()
         pointDistance_.clear();
     }
 
+    if (topologyFilterType::PROXIMITY == topoFilter_)
+    {
+        filterByProximity();
+    }
+
     if (debug)
     {
         print(Pout);
diff --git a/src/sampling/surface/distanceSurface/distanceSurface.H b/src/sampling/surface/distanceSurface/distanceSurface.H
index b56f49375b3362302bde2eb44bb90aff8771ded7..22738393291b3783e2b36059adca04144f461a9c 100644
--- a/src/sampling/surface/distanceSurface/distanceSurface.H
+++ b/src/sampling/surface/distanceSurface/distanceSurface.H
@@ -32,27 +32,82 @@ Description
     Uses an iso-surface algorithm (cell, topo, point) for constructing the
     distance surface.
 
+    For a zero-distance surface, it performs additional checks and supports
+    filtering to handle the surface boundaries.
+
 Usage
+    Example of function object partial specification:
+    \verbatim
+    surfaces
+    {
+        surface1
+        {
+            type        distanceSurface;
+            surfaceType triSurfaceMesh;
+            surfaceName something.obj;
+            topology    proximity;
+        }
+
+        surface2
+        {
+            type        distanceSurface;
+            surfaceType triSurfaceMesh;
+            surfaceName other.obj;
+
+            topology    nearestPoints;
+            nearestPoints
+            (
+                (0 0 0)
+                (10 10 0)
+            );
+
+            // Max search distance for nearestPoints
+            maxDistance 0.005;
+        }
+    }
+    \endverbatim
+
     Dictionary controls:
     \table
         Property | Description                              | Required | Default
-        distance | distance from surface                    | yes |
-        signed   | Use sign when distance is positive       | partly |
-        isoMethod | Iso-algorithm (cell/topo/point)         | no  | topo
+        distance | distance from surface                    | no  | 0
+        signed   | Use sign when distance is positive       | no  | true
+        isoMethod | Iso-algorithm (cell/topo/point)         | no  | default
         regularise | Face simplification (enum or bool)     | no  | true
         bounds   | Limit with bounding box                  | no  |
         surfaceType | Type of surface                       | yes |
         surfaceName | Name of surface in \c triSurface/     | no  | dict name
+        topology    | Topology filter (none/largestRegion/nearestPoints/proximity) | no  | none
+        nearestPoints | Points for point-based segmentation | no  |
+        maxDistance | Max search distance for nearestPoints | no  | GREAT
+        absProximity | Max proximity of face centres        | no  | 1e-5
     \endtable
 
+    Filtering (for zero-distance only)
+
+    - \c largestRegion (pre-filter):
+        The cut cells are checked for topological connectivity and the
+        region with the most number of cut cells is retained.
+        This handles the "ragged" edge problem.
+
+    - \c nearestPoints (pre-filter):
+        The cut cells split into regions, the regions closest to the
+        user-defined points are retained.
+        Uses \c maxDistance for additional control.
+
+    - \c proximity (post-filter):
+        Checks the resulting faces against the original search surface
+        and rejects faces with a distance greater than \c absProximity.
+    .
+
 Note
     For distance = 0, some special adjustments.
     - Always signed (ignoring the input value).
     - Use normal distance from surface (for better treatment of open edges).
-    - When the isoSurfaceCell algorithm is used, additional checks for open
-      surfaces edges are used to limit the extend of resulting distance
-      surface. The resulting surface elements will not, however, contain
-      partial cell coverage.
+    - Additional checks for open surfaces edges are used to limit the extend
+      of resulting distance surface.
+      The resulting surface elements will, however, contain partial cell
+      coverage. NB: Not applicable if the \c point isoMethod is used.
 
 The keyword \c cell (bool value) which was use in 1906 and earlier to switch
 between point/cell algorithms is now ignored (2020-12).
@@ -70,6 +125,7 @@ SourceFiles
 #include "sampledSurface.H"
 #include "searchableSurface.H"
 #include "isoSurfaceBase.H"
+#include "pointIndexHit.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -82,23 +138,53 @@ namespace Foam
 
 class distanceSurface
 {
+    // Data Types
+
+        //- The type of pre/post face-filtering
+        enum class topologyFilterType : uint8_t
+        {
+            NONE,               //!< No additional filtering
+            LARGEST_REGION,     //!< Retain largest region
+            NEAREST_POINTS,     //!< Retain regions nearest to the points
+            PROXIMITY           //!< Post-filter for surface proximity
+        };
+
+        //- Names for the topology filter
+        static const Enum<topologyFilterType> topoFilterNames_;
+
+
     // Private Data
 
         //- Reference to mesh
         const polyMesh& mesh_;
 
-        //- Surface
-        const autoPtr<searchableSurface> surfPtr_;
+        //- Searchable surface
+        const autoPtr<searchableSurface> geometryPtr_;
 
         //- Distance value
         const scalar distance_;
 
-        //- Signed distance
-        const bool signed_;
+        //- Distance is zero. Implies signed and additional optimizations
+        const bool withZeroDistance_;
+
+        //- Use signed distance
+        const bool withSignDistance_;
 
         //- Parameters for iso-surface (algorithm, filter, mergeTol, etc)
         isoSurfaceParams isoParams_;
 
+        //- Optional topology face-filtering
+        topologyFilterType topoFilter_;
+
+        //- Points for nearest-points segmentation
+        pointField nearestPoints_;
+
+        //- Max search distance squared (for nearestPoints)
+        scalar maxDistanceSqr_;
+
+        //- Max distance for proximity check (post-filtering)
+        scalar absProximity_;
+
         //- Distance to cell centres
         autoPtr<volScalarField> cellDistancePtr_;
 
@@ -118,6 +204,24 @@ class distanceSurface
         mutable autoPtr<isoSurfaceBase> isoSurfacePtr_;
 
 
+    // Private Member Functions
+
+        //- Absolute distances from hit points
+        //  Hit/miss checks have been done elsewhere.
+        static inline void calcAbsoluteDistance
+        (
+            scalarField& distance,
+            const pointField& points,
+            const List<pointIndexHit>& nearest
+        )
+        {
+            forAll(nearest, i)
+            {
+                distance[i] = Foam::mag(points[i] - nearest[i].point());
+            }
+        }
+
+
 protected:
 
     // Protected Member Functions
@@ -145,6 +249,29 @@ protected:
         }
 
 
+    // Private Member Functions
+
+        //- Re-filter the blocked cells based on the anticipated cuts
+        //  Uses a lightweight variant of cutting.
+        bool refineBlockedCells
+        (
+            bitSet& ignoreCells,
+            const isoSurfaceBase& isoContext
+        ) const;
+
+        //- Prepare blockedFaces for region split
+        bitSet filterPrepareRegionSplit(const bitSet& ignoreCells) const;
+
+        //- Adjust extracted iso-surface to remove far faces
+        void filterByProximity();
+
+        //- Keep region with the most cuts (after region split)
+        void filterKeepLargestRegion(bitSet& ignoreCells) const;
+
+        //- Keep region(s) closest to the nearest points
+        void filterKeepNearestRegions(bitSet& ignoreCells) const;
+
+
 public:
 
     //- Runtime type information
@@ -161,6 +288,16 @@ public:
             const dictionary& dict
         );
 
+        //- Construct from components with zero-distanced
+        distanceSurface
+        (
+            const polyMesh& mesh,
+            const word& surfaceType,
+            const word& surfaceName,
+            const isoSurfaceParams& params = isoSurfaceParams(),
+            const bool interpolate = false
+        );
+
         //- Construct from components
         distanceSurface
         (
@@ -186,11 +323,11 @@ public:
         //- The name of the underlying searchableSurface
         const word& surfaceName() const
         {
-            return surfPtr_->name();
+            return geometryPtr_->name();
         }
 
         //- The distance to the underlying searchableSurface
-        scalar distance() const
+        scalar distance() const noexcept
         {
             return distance_;
         }
diff --git a/src/sampling/surface/distanceSurface/distanceSurfaceFilter.C b/src/sampling/surface/distanceSurface/distanceSurfaceFilter.C
new file mode 100644
index 0000000000000000000000000000000000000000..9599058302400ac6dc8ee707ca9b6996880841fd
--- /dev/null
+++ b/src/sampling/surface/distanceSurface/distanceSurfaceFilter.C
@@ -0,0 +1,421 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "distanceSurface.H"
+#include "regionSplit.H"
+#include "syncTools.H"
+#include "vtkSurfaceWriter.H"
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+bool Foam::distanceSurface::refineBlockedCells
+(
+    bitSet& ignoreCells,
+    const isoSurfaceBase& isoCutter
+) const
+{
+    // With the cell/point distance fields we can take a second pass at
+    // pre-filtering.
+    // This duplicates how cut detection is determined in the cell/topo
+    // algorithms but is fairly inexpensive (creates no geometry)
+
+    bool changed = false;
+
+    for (label celli = 0; celli < mesh_.nCells(); ++celli)
+    {
+        if (ignoreCells.test(celli))
+        {
+            continue;
+        }
+
+        auto cut = isoCutter.getCellCutType(celli);
+        if (!(cut & isoSurfaceBase::ANYCUT))
+        {
+            ignoreCells.set(celli);
+            changed = true;
+        }
+    }
+
+    return changed;
+}
+
+
+Foam::bitSet Foam::distanceSurface::filterPrepareRegionSplit
+(
+    const bitSet& ignoreCells
+) const
+{
+    // Prepare for region split
+
+    bitSet blockedFaces(mesh_.nFaces());
+
+    const labelList& faceOwn = mesh_.faceOwner();
+    const labelList& faceNei = mesh_.faceNeighbour();
+
+    // Could be more efficient
+    for (label facei = 0; facei < mesh_.nInternalFaces(); ++facei)
+    {
+        // If only one cell is blocked, the face corresponds
+        // to an exposed subMesh face
+
+        if
+        (
+            ignoreCells.test(faceOwn[facei])
+         != ignoreCells.test(faceNei[facei])
+        )
+        {
+            blockedFaces.set(facei);
+        }
+    }
+
+    for (const polyPatch& patch : mesh_.boundaryMesh())
+    {
+        if (!patch.coupled())
+        {
+            continue;
+        }
+
+        forAll(patch, patchFacei)
+        {
+            const label facei = patch.start() + patchFacei;
+            if (ignoreCells.test(faceOwn[facei]))
+            {
+                blockedFaces.set(facei);
+            }
+        }
+    }
+
+    syncTools::syncFaceList(mesh_, blockedFaces, xorEqOp<unsigned int>());
+
+    return blockedFaces;
+}
+
+
+void Foam::distanceSurface::filterKeepLargestRegion
+(
+    bitSet& ignoreCells
+) const
+{
+    // For region split
+    bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
+
+    // Split region
+    regionSplit rs(mesh_, blockedFaces);
+    blockedFaces.clearStorage();
+
+    const labelList& regionColour = rs;
+
+    // Identical number of regions on all processors
+    labelList nCutsPerRegion(rs.nRegions(), Zero);
+
+    // Count cut cells (ie, unblocked)
+    forAll(regionColour, celli)
+    {
+        if (!ignoreCells.test(celli))
+        {
+            ++nCutsPerRegion[regionColour[celli]];
+        }
+    }
+
+    // Sum totals from all processors
+    Pstream::listCombineGather(nCutsPerRegion, plusEqOp<label>());
+
+
+    // Define which regions to keep
+    boolList keepRegion(rs.nRegions(), false);
+
+    if (Pstream::master())
+    {
+        const label largest = findMax(nCutsPerRegion);
+
+        if (largest == -1)
+        {
+            // Should not happen
+            keepRegion = true;
+        }
+        else
+        {
+            keepRegion[largest] = true;
+        }
+
+        if (debug)
+        {
+            Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
+                << nCutsPerRegion.size() << " regions, largest is "
+                << largest <<  ": " << flatOutput(nCutsPerRegion) << nl;
+        }
+    }
+
+    Pstream::scatter(keepRegion);
+
+    forAll(regionColour, celli)
+    {
+        if (!keepRegion.test(regionColour[celli]))
+        {
+            ignoreCells.set(celli);
+        }
+    }
+}
+
+
+void Foam::distanceSurface::filterKeepNearestRegions
+(
+    bitSet& ignoreCells
+) const
+{
+    if (nearestPoints_.empty())
+    {
+        WarningInFunction
+            << "Ignoring nearestPoints - no points provided" << nl
+            << endl;
+        return;
+    }
+
+    // For region split
+    bitSet blockedFaces(filterPrepareRegionSplit(ignoreCells));
+
+    // Split region
+    regionSplit rs(mesh_, blockedFaces);
+    blockedFaces.clearStorage();
+
+    const labelList& regionColour = rs;
+
+    const pointField& cc = mesh_.cellCentres();
+    const pointField& nearPts = nearestPoints_;
+
+    // The magSqr distance and region
+    typedef Tuple2<scalar, label> nearInfo;
+    List<nearInfo> nearest(nearPts.size(), nearInfo(GREAT, -1));
+
+    // Also collect cuts per region, may be useful for rejecting
+    // small regions. Code as per filterKeepLargestRegion
+    labelList nCutsPerRegion(rs.nRegions(), Zero);
+
+    forAll(cc, celli)
+    {
+        if (ignoreCells.test(celli))
+        {
+            continue;
+        }
+
+        const point& pt = cc[celli];
+        const label regioni = regionColour[celli];
+
+        ++nCutsPerRegion[regioni];
+
+        label pointi = 0;
+        for (nearInfo& near : nearest)
+        {
+            const scalar distSqr = magSqr(nearPts[pointi] - pt);
+            ++pointi;
+
+            if (distSqr < near.first())
+            {
+                near.first() = distSqr;
+                near.second() = regioni;
+            }
+        }
+    }
+
+    // Sum totals from all processors
+    Pstream::listCombineGather(nCutsPerRegion, plusEqOp<label>());
+
+    // Get nearest
+    Pstream::listCombineGather(nearest, minFirstEqOp<scalar>());
+
+
+    // Define which regions to keep
+
+    boolList keepRegion(rs.nRegions(), false);
+
+    if (Pstream::master())
+    {
+        const label largest = findMax(nCutsPerRegion);
+
+        for (const nearInfo& near : nearest)
+        {
+            const scalar distSqr = near.first();
+            const label regioni = near.second();
+
+            if (regioni != -1 && distSqr < maxDistanceSqr_)
+            {
+                keepRegion[regioni] = true;
+            }
+        }
+
+        if (debug)
+        {
+            Info<< "Had " << sum(nCutsPerRegion) << " cuts, in "
+                << nCutsPerRegion.size() << " regions, largest is "
+                << largest <<  ": " << flatOutput(nCutsPerRegion) << nl;
+
+            Info<< "nearestPoints (max distance = "
+                << sqrt(maxDistanceSqr_) << ")" << nl;
+
+            forAll(nearPts, pointi)
+            {
+                const scalar dist = sqrt(nearest[pointi].first());
+                const label regioni = nearest[pointi].second();
+
+                Info<< "    " << nearPts[pointi] << " region "
+                    << regioni << " distance "
+                    << dist;
+
+                if (!keepRegion.test(regioni))
+                {
+                    Info<< " too far";
+                }
+                Info<< nl;
+            }
+        }
+    }
+
+    Pstream::scatter(keepRegion);
+
+    forAll(regionColour, celli)
+    {
+        if (!keepRegion.test(regionColour[celli]))
+        {
+            ignoreCells.set(celli);
+        }
+    }
+}
+
+
+void Foam::distanceSurface::filterByProximity()
+{
+    const searchableSurface& geom = geometryPtr_();
+
+    // Filtering for faces
+    const pointField& fc = surface_.faceCentres();
+
+    bitSet faceSelection(fc.size());
+    label nTrimmed = 0;
+
+
+    // For each face
+    scalarField faceDistance(fc.size(), GREAT);
+    scalarField faceNormalDistance;  // Debugging only
+    {
+        List<pointIndexHit> nearest;
+        geom.findNearest
+        (
+            fc,
+            // Use initialized field (GREAT) to limit search too
+            faceDistance,
+            nearest
+        );
+        calcAbsoluteDistance(faceDistance, fc, nearest);
+
+        // Heavier debugging
+        if (debug & 4)
+        {
+            vectorField norms;
+            geom.getNormal(nearest, norms);
+
+            faceNormalDistance.resize(fc.size());
+
+            forAll(nearest, i)
+            {
+                const vector diff(fc[i] - nearest[i].point());
+
+                faceNormalDistance[i] = Foam::mag((diff & norms[i]));
+            }
+        }
+    }
+
+    // Note
+    // Proximity checks using the face points (nearest hit) to establish
+    // a length scale are too fragile. Can easily have stretched faces
+    // where the centre is less than say 0.3-0.5 of the centre-point distance
+    // but they are still outside.
+
+    // Using the absolute proximity of the face centres is more robust.
+
+
+    // Consider the absolute proximity of the face centres
+    forAll(faceDistance, facei)
+    {
+        if (faceDistance[facei] <= absProximity_)
+        {
+            faceSelection.set(facei);
+        }
+        else
+        {
+            ++nTrimmed;
+
+            if (debug & 2)
+            {
+                Pout<< "trim reject: "
+                    << faceDistance[facei] << nl;
+            }
+        }
+    }
+
+
+    // Heavier debugging
+    if (debug & 4)
+    {
+        labelField faceFilterStatus(faceSelection.size(), Zero);
+
+        for (const label facei : faceSelection)
+        {
+            faceFilterStatus[facei] = 1;
+        }
+
+        const fileName outputName(surfaceName() + "-proximity-filter");
+
+        Info<< "Writing debug surface: " << outputName << nl;
+
+        surfaceWriters::vtkWriter writer
+        (
+            surface_.points(),
+            surface_,  // faces
+            outputName
+        );
+
+        writer.write("absolute-distance", faceDistance);
+        writer.write("normal-distance", faceNormalDistance);
+        writer.write("filter-state", faceFilterStatus);
+    }
+
+
+    if (returnReduce(nTrimmed, sumOp<label>()) != 0)
+    {
+        labelList pointMap, faceMap;
+        meshedSurface filtered
+        (
+            surface_.subsetMesh(faceSelection, pointMap, faceMap)
+        );
+        surface_.transfer(filtered);
+
+        meshCells_ = UIndirectList<label>(meshCells_, faceMap)();
+    }
+}
+
+
+// ************************************************************************* //