I am trying to simplify my geometry using TopologySimplifier to simplify my shapefile , but after this I am getting so many sliver/gaps( even trying with different tolerance (.001 to .005). Below is the code for same.I have also tried DP Simplifier , but the result was very bad.Tried buffer(0)/Snapper also but no luck. The shape file is also very big as it is Census data file. Is there any way I can fix this?

enter image description here

public static SimpleFeatureCollection simplify(ShapefileDataStore mergedData, SimpleFeatureCollection collA,
        String attrName) throws IOException {
    Double tolerance = 0.003;
    SimpleFeatureBuilder builder = new SimpleFeatureBuilder(collA.getSchema());
    List<SimpleFeature> ret = new ArrayList<>();
    FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
        try (SimpleFeatureIterator itr = collA.features()) {
            while (itr.hasNext()) {
                SimpleFeature f = itr.next();
                    Geometry disGeom = (Geometry) f.getDefaultGeometry();
                if(disGeom == null) {
                Geometry g1 = TopologyPreservingSimplifier.simplify(disGeom, tolerance);
                String geomName = collA.getSchema().getGeometryDescriptor().getLocalName();
                builder.set(geomName, g1);
                SimpleFeature fout = builder.buildFeature(null);
    return DataUtilities.collection(ret);
  • just use a smaller tolerance
    – Ian Turton
    Aug 9, 2021 at 9:34
  • Thanks for the suggestion Ian but the problem with the very small tolerance is that , it might fix it but the number of vertices will increase. My shapefile has 38 million vertices and after simplification with very small tolerance it is coming as 8 million and I want my shapefile with approx. 1-2 million vertices. So is there any workaround where on the resultant geometry ,I can identify and fix those slivers. Aug 9, 2021 at 9:45
  • Not that I know of in JTS or GeoTools, I would stop using a ShapeFile if it's a problem with size
    – Ian Turton
    Aug 9, 2021 at 9:49
  • 2
    OpenJUMP has a tool for simplifying a polygon layer and it preserves the topology of the whole layer. The tool is based on JTS and it builds first a linear graph from the polygon boundaries, simplifies this new line layer, builds new polygons from simplified lines, and transfers the attributes from the original data. It is Java and potentially possible to use with GeoTools. The license is GPL 2.
    – user30184
    Aug 9, 2021 at 10:07
  • Thanks for the suggestion , I did try with OpenJump , but internally it was using the TopologyPreserveSimplifier and produced the same result with slivers Aug 9, 2021 at 17:20

2 Answers 2


TL;DR - Full code is here

So, the trick here is to break up the polygons into their shared line segments before you simplify them and then put them back together.

To find the shared elements:

try (SimpleFeatureIterator it = index.features()) {
  String name = index.getSchema().getGeometryDescriptor().getName().toString();
  while (it.hasNext()) {
    SimpleFeature feature = it.next();
    Geometry g = ((Geometry) feature.getDefaultGeometry());
    Filter filter = FF.intersects(FF.property(name), FF.literal(g));
    SimpleFeatureCollection neighbourhood = index.subCollection(filter);
    String id = feature.getID();

    List<MultiLineString> shared = new ArrayList<>();
    try (SimpleFeatureIterator itr2 = neighbourhood.features()) {
      while (itr2.hasNext()) {
        SimpleFeature next = itr2.next();
        if (next.getID().equalsIgnoreCase(id))
        String nKey = next.getID();
        String right;
        String left;
        if (id.compareTo(nKey) < 0) {
          left = id;
          right = nKey;
        } else {
          left = nKey;
          right = id;
        Geometry intersection = g.intersection(((Geometry) next.getDefaultGeometry()));
        LineMerger merger = new LineMerger();
        Collection<LineString> mergedLineStrings = new ArrayList<>();
        if (intersection instanceof MultiLineString) {
          mergedLineStrings = merger.getMergedLineStrings();

        } else if (intersection instanceof LineString) {
          mergedLineStrings.add((LineString) intersection);
        for (LineString l : mergedLineStrings) {
          MultiLineString multiLine = GF
          Edge e = new Edge(left, right, multiLine);

But you also need the unshared elements of any polygons on the edge (and islands):

      Geometry sharedGeom = GF.createGeometryCollection(GeometryFactory.toGeometryArray(shared));
      if (sharedGeom instanceof GeometryCollection) {
        ArrayList<LineString> lines = new ArrayList<>();
        for (int k = 0; k < sharedGeom.getNumGeometries(); k++) {
          Geometry geometryN = sharedGeom.getGeometryN(k);
          if (geometryN instanceof LineString) {
            lines.add((LineString) geometryN);
          } else if (geometryN instanceof MultiLineString) {
            for (int kk = 0; kk < geometryN.getNumGeometries(); kk++) {
              lines.add((LineString) geometryN.getGeometryN(kk));
        sharedGeom = GF.createMultiLineString(GeometryFactory.toLineStringArray(lines));
      Geometry exRing;
      if (g instanceof Polygon) {
        exRing = ((Polygon) g).getExteriorRing();
        Geometry unshared = exRing.difference(sharedGeom);
        insertUnsharedEdge(id, unshared);
      } else if (g instanceof MultiPolygon) {
        MultiPolygon mp = (MultiPolygon) g;
        for (int i = 0; i < mp.getNumGeometries(); i++) {
          Polygon p = (Polygon) mp.getGeometryN(i);
          exRing = p.getExteriorRing();
          if (exRing instanceof GeometryCollection) {
            for (int j = 0; j < exRing.getNumGeometries(); j++) {
              Geometry unshared = exRing.getGeometryN(j).difference(sharedGeom);
              insertUnsharedEdge(id, unshared);
          } else {
            Geometry unshared = exRing.difference(sharedGeom);
            insertUnsharedEdge(id, unshared);


You can then simplify the edges (TopologyPreservingSimplifier works best as you don't loose any polygons that way).

Finally, stitching the edges back into polygons makes use of the JTS Polygonizer and updates the feature:

private SimpleFeatureCollection reassemblePolygons(SimpleFeatureCollection collection) {
ArrayList<SimpleFeature> ret = new ArrayList<>();
// for each feature we need to rebuild it's geometry
try (SimpleFeatureIterator itr = collection.features()) {
  while (itr.hasNext()) {
    SimpleFeature feature = itr.next();

    Polygonizer polygonizer = new Polygonizer();
    String id = feature.getID();
    for (Edge l : edges) {
      if (l.getLeftId().equalsIgnoreCase(id) || l.getRightId().equalsIgnoreCase(id)) {


return DataUtilities.collection(ret);


Here's what the Arkansas Mississippi border looks like:

enter image description here

Full code is here

  • Thanks Ian for providing the details explanation with code. This approach really maintaining the topology , but not sure why it is creating several holes in the complete Map . I have attached the small example in a separate question (gis.stackexchange.com/questions/408457/…) Aug 16, 2021 at 20:52

JTS now has the CoverageSimplifier class to simplify polygonal coverages and maintain the topology. See this blog post for details.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.