diff --git a/Applications/Utils/MeshEdit/CMakeLists.txt b/Applications/Utils/MeshEdit/CMakeLists.txt
index 1da08d52564b28f66526c60fc1548406174559a8..39327f0445e6bc82bf13d2423211c75c03b74ae9 100644
--- a/Applications/Utils/MeshEdit/CMakeLists.txt
+++ b/Applications/Utils/MeshEdit/CMakeLists.txt
@@ -14,6 +14,7 @@ set(TOOLS
     moveMeshNodes
     NodeReordering
     queryMesh
+    RemoveGhostNodes
     removeMeshElements
     ResetPropertiesInPolygonalRegion
     reviseMesh
diff --git a/Applications/Utils/MeshEdit/RemoveGhostNodes.cpp b/Applications/Utils/MeshEdit/RemoveGhostNodes.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6ae25278943ae330f496822832ca1c4848b946c7
--- /dev/null
+++ b/Applications/Utils/MeshEdit/RemoveGhostNodes.cpp
@@ -0,0 +1,99 @@
+/**
+ * \file
+ *
+ * @copyright
+ * Copyright (c) 2012-2020, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/LICENSE.txt
+ */
+
+#include <tclap/CmdLine.h>
+
+#include "Applications/ApplicationsLib/LogogSetup.h"
+#include "InfoLib/GitInfo.h"
+
+#include "MeshLib/IO/writeMeshToFile.h"
+#include "vtkCleanUnstructuredGrid.h"
+
+#include <vtkSmartPointer.h>
+#include <vtkUnstructuredGrid.h>
+#include <vtkXMLPUnstructuredGridReader.h>
+#include <vtkRemoveGhosts.h>
+
+int main (int argc, char* argv[])
+{
+    ApplicationsLib::LogogSetup logog_setup;
+/*
+    TCLAP::CmdLine cmd(
+        "Reads a 3D unstructured mesh and samples it onto a structured grid of "
+        "the same extent. Cell properties are mapped onto the grid (sampled at "
+        "the centre-points of each cube), node properties are ignored. Note, "
+        "that a large cube size may result in an undersampling of the original "
+        "mesh structure.\nCube sizes are defines by x/y/z-parameters. For "
+        "equilateral cubes, only the x-parameter needs to be set.\n\n"
+        "OpenGeoSys-6 software, version " +
+            GitInfoLib::GitInfo::ogs_version +
+            ".\n"
+            "Copyright (c) 2012-2020, OpenGeoSys Community "
+            "(http://www.opengeosys.org)",
+        ' ', GitInfoLib::GitInfo::ogs_version);
+
+    TCLAP::ValueArg<double> z_arg("z", "cellsize-z",
+                                  "edge length of cubes in z-direction (depth)",
+                                  false, 1000, "floating point number");
+    cmd.add(z_arg);
+
+    TCLAP::ValueArg<double> y_arg(
+        "y", "cellsize-y", "edge length of cubes in y-direction (latitude)",
+        false, 1000, "floating point number");
+    cmd.add(y_arg);
+
+    TCLAP::ValueArg<double> x_arg(
+        "x", "cellsize-x",
+        "edge length of cubes in x-direction (longitude) or all directions, if "
+        "y and z are not set",
+        true, 1000, "floating point number");
+    cmd.add(x_arg);
+
+    TCLAP::ValueArg<std::string> output_arg(
+        "o", "output", "the output grid (*.vtu)", true, "", "output.vtu");
+    cmd.add(output_arg);
+
+    TCLAP::ValueArg<std::string> input_arg("i", "input",
+                                           "the 3D input mesh (*.vtu, *.msh)",
+                                           true, "", "input.vtu");
+    cmd.add(input_arg);
+    cmd.parse(argc, argv);
+
+    if ((y_arg.isSet() && !z_arg.isSet()) ||
+        ((!y_arg.isSet() && z_arg.isSet())))
+    {
+        ERR("For equilateral cubes, only x needs to be set. For unequal "
+            "cuboids, all three edge lengths (x/y/z) need to be specified.")
+        return -1;
+    }
+
+    double const x_size = x_arg.getValue();
+    double const y_size = (y_arg.isSet()) ? y_arg.getValue() : x_arg.getValue();
+    double const z_size = (z_arg.isSet()) ? z_arg.getValue() : x_arg.getValue();
+    std::array<double, 3> const cellsize = { x_size, y_size, z_size };
+*/
+    std::string input_file = "c:/Projects/RemoveGhostNodes/Mesh3D.pvtu";
+    vtkSmartPointer<vtkXMLPUnstructuredGridReader> reader =
+        vtkSmartPointer<vtkXMLPUnstructuredGridReader>::New();
+    reader->SetFileName(input_file.c_str());
+    //reader->SetFileName(input_arg.getValue().c_str());
+    reader->Update();
+    vtkSmartPointer<vtkUnstructuredGrid> mesh = reader->GetOutput();
+
+    vtkSmartPointer<vtkRemoveGhosts> ghosts =
+        vtkSmartPointer<vtkRemoveGhosts>::New();
+    ghosts->SetInputConnection(reader->GetOutputPort());
+
+
+    //if (MeshLib::IO::writeMeshToFile(*grid, output_arg.getValue()) != 0)
+    //    return EXIT_FAILURE;
+
+    return EXIT_SUCCESS;
+}
diff --git a/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.cpp b/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..37d6febe325375dcab617586d5750c4b377cd9f0
--- /dev/null
+++ b/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.cpp
@@ -0,0 +1,232 @@
+/*=========================================================================
+
+Program:   ParaView
+Module:    vtkCleanUnstructuredGrid.cxx
+
+Copyright (c) Kitware, Inc.
+All rights reserved.
+See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
+
+This software is distributed WITHOUT ANY WARRANTY; without even
+the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+#include "vtkCleanUnstructuredGrid.h"
+
+#include "vtkCell.h"
+#include "vtkCellData.h"
+#include "vtkCollection.h"
+#include "vtkDataSet.h"
+#include "vtkIncrementalPointLocator.h"
+#include "vtkInformation.h"
+#include "vtkInformationVector.h"
+#include "vtkIntArray.h"
+#include "vtkMergePoints.h"
+#include "vtkObjectFactory.h"
+#include "vtkPointData.h"
+#include "vtkPointSet.h"
+#include "vtkPoints.h"
+#include "vtkRectilinearGrid.h"
+#include "vtkUnstructuredGrid.h"
+
+vtkStandardNewMacro(vtkCleanUnstructuredGrid);
+vtkCxxSetObjectMacro(vtkCleanUnstructuredGrid, Locator, vtkIncrementalPointLocator);
+
+//----------------------------------------------------------------------------
+vtkCleanUnstructuredGrid::~vtkCleanUnstructuredGrid()
+{
+  this->SetLocator(nullptr);
+}
+
+//----------------------------------------------------------------------------
+void vtkCleanUnstructuredGrid::PrintSelf(ostream& os, vtkIndent indent)
+{
+  this->Superclass::PrintSelf(os, indent);
+}
+
+//----------------------------------------------------------------------------
+int vtkCleanUnstructuredGrid::RequestData(vtkInformation* vtkNotUsed(request),
+  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
+{
+  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
+  vtkInformation* outInfo = outputVector->GetInformationObject(0);
+
+  vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
+  vtkUnstructuredGrid* output =
+    vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
+
+  if (input->GetNumberOfCells() == 0)
+  {
+    // set up a ugrid with same data arrays as input, but
+    // no points, cells or data.
+    output->Allocate(1);
+    output->GetPointData()->CopyAllocate(input->GetPointData(), VTK_CELL_SIZE);
+    output->GetCellData()->CopyAllocate(input->GetCellData(), 1);
+    vtkPoints* pts = vtkPoints::New();
+    output->SetPoints(pts);
+    pts->Delete();
+    return 1;
+  }
+
+  output->GetPointData()->CopyAllocate(input->GetPointData());
+  output->GetCellData()->PassData(input->GetCellData());
+
+  // First, create a new points array that eliminate duplicate points.
+  // Also create a mapping from the old point id to the new.
+  vtkPoints* newPts = vtkPoints::New();
+
+  // Set the desired precision for the points in the output.
+  if (this->OutputPointsPrecision == vtkAlgorithm::DEFAULT_PRECISION)
+  {
+    // The logical behaviour would be to use the data type from the input.
+    // However, input is a vtkDataSet, which has no point data type; only the
+    // derived class vtkPointSet has a vtkPoints attribute, so only for that
+    // the logical practice can be applied, while for others (currently
+    // vtkImageData and vtkRectilinearGrid) the data type is the default
+    // for vtkPoints - which is VTK_FLOAT.
+    vtkPointSet* ps = vtkPointSet::SafeDownCast(input);
+    if (ps)
+    {
+      newPts->SetDataType(ps->GetPoints()->GetDataType());
+    }
+  }
+  else if (this->OutputPointsPrecision == vtkAlgorithm::SINGLE_PRECISION)
+  {
+    newPts->SetDataType(VTK_FLOAT);
+  }
+  else if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION)
+  {
+    newPts->SetDataType(VTK_DOUBLE);
+  }
+
+  vtkIdType num = input->GetNumberOfPoints();
+  vtkIdType id;
+  vtkIdType newId;
+  vtkIdType* ptMap = new vtkIdType[num];
+  double pt[3];
+
+  this->CreateDefaultLocator(input);
+  if (this->ToleranceIsAbsolute)
+  {
+    this->Locator->SetTolerance(this->AbsoluteTolerance);
+  }
+  else
+  {
+    this->Locator->SetTolerance(this->Tolerance * input->GetLength());
+  }
+  double bounds[6];
+  input->GetBounds(bounds);
+  this->Locator->InitPointInsertion(newPts, bounds);
+
+  vtkIdType progressStep = num / 100;
+  if (progressStep == 0)
+  {
+    progressStep = 1;
+  }
+  for (id = 0; id < num; ++id)
+  {
+    if (id % progressStep == 0)
+    {
+      this->UpdateProgress(0.8 * ((float)id / num));
+    }
+    input->GetPoint(id, pt);
+    if (this->Locator->InsertUniquePoint(pt, newId))
+    {
+      output->GetPointData()->CopyData(input->GetPointData(), id, newId);
+    }
+    ptMap[id] = newId;
+  }
+  output->SetPoints(newPts);
+  newPts->Delete();
+
+  // Now copy the cells.
+  vtkIdList* cellPoints = vtkIdList::New();
+  num = input->GetNumberOfCells();
+  output->Allocate(num);
+  for (id = 0; id < num; ++id)
+  {
+    if (id % progressStep == 0)
+    {
+      this->UpdateProgress(0.8 + 0.2 * ((float)id / num));
+    }
+    // special handling for polyhedron cells
+    if (vtkUnstructuredGrid::SafeDownCast(input) && input->GetCellType(id) == VTK_POLYHEDRON)
+    {
+      vtkUnstructuredGrid::SafeDownCast(input)->GetFaceStream(id, cellPoints);
+      vtkUnstructuredGrid::ConvertFaceStreamPointIds(cellPoints, ptMap);
+    }
+    else
+    {
+      input->GetCellPoints(id, cellPoints);
+      for (int i = 0; i < cellPoints->GetNumberOfIds(); i++)
+      {
+        int cellPtId = cellPoints->GetId(i);
+        newId = ptMap[cellPtId];
+        cellPoints->SetId(i, newId);
+      }
+    }
+    output->InsertNextCell(input->GetCellType(id), cellPoints);
+  }
+
+  delete[] ptMap;
+  cellPoints->Delete();
+  output->Squeeze();
+
+  return 1;
+}
+
+//----------------------------------------------------------------------------
+int vtkCleanUnstructuredGrid::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
+{
+  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
+  return 1;
+}
+
+//----------------------------------------------------------------------------
+void vtkCleanUnstructuredGrid::CreateDefaultLocator(vtkDataSet* input)
+{
+  double tol;
+  if (this->ToleranceIsAbsolute)
+  {
+    tol = this->AbsoluteTolerance;
+  }
+  else
+  {
+    if (input)
+    {
+      tol = this->Tolerance * input->GetLength();
+    }
+    else
+    {
+      tol = this->Tolerance;
+    }
+  }
+
+  if (this->Locator == nullptr)
+  {
+    if (tol == 0.0)
+    {
+      this->Locator = vtkMergePoints::New();
+      this->Locator->Register(this);
+      this->Locator->Delete();
+    }
+    else
+    {
+      this->Locator = vtkPointLocator::New();
+      this->Locator->Register(this);
+      this->Locator->Delete();
+    }
+  }
+  else
+  {
+    // check that the tolerance wasn't changed from zero to non-zero
+    if ((tol > 0.0) && (this->GetLocator()->GetTolerance() == 0.0))
+    {
+      this->SetLocator(nullptr);
+      this->Locator = vtkPointLocator::New();
+      this->Locator->Register(this);
+      this->Locator->Delete();
+    }
+  }
+}
diff --git a/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.h b/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.h
new file mode 100644
index 0000000000000000000000000000000000000000..e13ad9522fbf2c13bf869cec3f7ca72e7f68b3e3
--- /dev/null
+++ b/Applications/Utils/MeshEdit/vtkCleanUnstructuredGrid.h
@@ -0,0 +1,108 @@
+/*=========================================================================
+
+  Program:   ParaView
+  Module:    vtkCleanUnstructuredGrid.h
+
+  Copyright (c) Kitware, Inc.
+  All rights reserved.
+  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+     PURPOSE.  See the above copyright notice for more information.
+
+=========================================================================*/
+
+/**
+ * @class   vtkCleanUnstructuredGrid
+ * @brief   merge duplicate points
+ *
+ *
+ * vtkCleanUnstructuredGrid is a filter that takes unstructured grid data as
+ * input and generates unstructured grid data as output. vtkCleanUnstructuredGrid can
+ * merge duplicate points (with coincident coordinates) using the vtkMergePoints object
+ * to merge points.
+ *
+ * @sa
+ * vtkCleanPolyData
+*/
+
+#ifndef vtkCleanUnstructuredGrid_h
+#define vtkCleanUnstructuredGrid_h
+
+//#include "vtkPVVTKExtensionsFiltersGeneralModule.h" //needed for exports
+#include "vtkUnstructuredGridAlgorithm.h"
+
+class vtkIncrementalPointLocator;
+class vtkDataSet;
+
+class /*VTKPVVTKEXTENSIONSFILTERSGENERAL_EXPORT*/ vtkCleanUnstructuredGrid
+  : public vtkUnstructuredGridAlgorithm
+{
+public:
+  static vtkCleanUnstructuredGrid* New();
+
+  vtkTypeMacro(vtkCleanUnstructuredGrid, vtkUnstructuredGridAlgorithm);
+
+  // By default ToleranceIsAbsolute is false and Tolerance is
+  // a fraction of Bounding box diagonal, if true, AbsoluteTolerance is
+  // used when adding points to locator (merging)
+  vtkSetMacro(ToleranceIsAbsolute, bool);
+  vtkBooleanMacro(ToleranceIsAbsolute, bool);
+  vtkGetMacro(ToleranceIsAbsolute, bool);
+
+  // Specify tolerance in terms of fraction of bounding box length.
+  // Default is 0.0.
+  vtkSetClampMacro(Tolerance, double, 0.0, 1.0);
+  vtkGetMacro(Tolerance, double);
+
+  // Specify tolerance in absolute terms. Default is 1.0.
+  vtkSetClampMacro(AbsoluteTolerance, double, 0.0, VTK_DOUBLE_MAX);
+  vtkGetMacro(AbsoluteTolerance, double);
+
+  //@{
+  /**
+   * Set/Get a spatial locator for speeding the search process. By
+   * default an instance of vtkMergePoints is used.
+   */
+  virtual void SetLocator(vtkIncrementalPointLocator* locator);
+  vtkGetObjectMacro(Locator, vtkIncrementalPointLocator);
+  //@}
+
+  // Create default locator. Used to create one when none is specified.
+  void CreateDefaultLocator(vtkDataSet* input = nullptr);
+
+  // Release locator
+  void ReleaseLocator() { this->SetLocator(nullptr); }
+
+  //@{
+  /**
+   * Set/get the desired precision for the output types. See the documentation
+   * for the vtkAlgorithm::DesiredOutputPrecision enum for an explanation of
+   * the available precision settings.
+   */
+  vtkSetMacro(OutputPointsPrecision, int);
+  vtkGetMacro(OutputPointsPrecision, int);
+  //@}
+
+  void PrintSelf(ostream& os, vtkIndent indent) override;
+
+protected:
+  vtkCleanUnstructuredGrid() = default;
+  ~vtkCleanUnstructuredGrid() override;
+
+  // options for managing point merging tolerance
+  bool ToleranceIsAbsolute = false;
+  double Tolerance = 0.0;
+  double AbsoluteTolerance = 1.0;
+  vtkIncrementalPointLocator* Locator = nullptr;
+  int OutputPointsPrecision = vtkAlgorithm::DEFAULT_PRECISION;
+
+  int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
+  int FillInputPortInformation(int port, vtkInformation* info) override;
+
+private:
+  vtkCleanUnstructuredGrid(const vtkCleanUnstructuredGrid&) = delete;
+  void operator=(const vtkCleanUnstructuredGrid&) = delete;
+};
+#endif