Leaflet – Split a Polygon into Multiple Polygons by Multiple Line Strings in Leaflet and Turf.js

leafletturf

I have need to split a polygon into multiple polygons based on the line strings drawn on the polygon. I have come across another post that splits a polygon into two based one line string. If a new line is drawn then the existing lines are ignored and the polygon is split into two based on the new line. What I want to achieve is 'split a polygon into multiple polygons based on multiple line.

 var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  var osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors';
  var osm = L.tileLayer(osmUrl, { maxZoom: 18, attribution: osmAttrib });
  var drawnItems = L.featureGroup();

  var map = new L.Map('map', { center: new L.LatLng(51.505, -0.04), zoom: 13 });

  osm.addTo(map);
  drawnItems.addTo(map);

  map.addControl(new L.Control.Draw({
    draw: {
      marker: false,
      circle: false,
      circlemarker: false,
      rectangle: false,
      polygon: {
        allowIntersection: false,
        showArea: true
      }
    }
  }));

  function cutPolygon(polygon, line, direction, id) {
    var i = -1;
    var j;
    var polyCoords = [];
    var retVal = null;

    if ((polygon.type != 'Polygon') || (line.type != 'LineString')) return retVal;
    if (line.coordinates.length != 2) return retVal;

    var intersectPoints = turf.lineIntersect(polygon, line);
    var nPoints = intersectPoints.features.length;
    if ((nPoints == 0) || ((nPoints % 2) != 0)) return retVal;

    var offsetLine = turf.lineOffset(line, (0.01 * direction), {units: 'kilometers'});
    var thickLineCorners = turf.featureCollection([line, offsetLine]);
    var thickLinePolygon = turf.convex(turf.explode(thickLineCorners));

    var clipped = turf.difference(polygon, thickLinePolygon);

    for (j = 0; j < clipped.geometry.coordinates.length; j++) {
      var polyg = turf.polygon(clipped.geometry.coordinates[j]);
      var overlap = turf.lineOverlap(polyg, line, {tolerance: 0.005});
      if (overlap.features.length > 0) {
        polyCoords[++i] = turf.coordAll(polyg);
      };
    };

    if (i == 0)
      retVal = turf.polygon(polyCoords, {id: id});
    else if (i > 0) {
      retVal = turf.multiPolygon([polyCoords], {id: id});
    }

    return retVal;
  };

  var polygons = [];
  var layers = [];

  map.on(L.Draw.Event.CREATED, function (event) {
    var layer = event.layer;
    drawnItems.addLayer(layer);

    var geojson = layer.toGeoJSON();
    var geom = turf.getGeom(geojson);

    if (geom.type == 'Polygon')
      polygons.push(geom);
    else if (geom.type == 'LineString') {
      var line = geom;
      layers.forEach(function (layer, index) {
        layer.remove();
      });
      layers = [];
      polygons.forEach(function (polygon, index) {
        var layer;
        var upperCut = cutPolygon(polygon, line, 1, 'upper');
        if (upperCut != null) {
          layer = L.geoJSON(upperCut, {
            style: function(feature) {
              return {color: 'green' };
            }
          }).addTo(map);   
          layers.push(layer);
        };
        var lowerCut = cutPolygon(polygon, line, -1, 'lower');
        if (lowerCut != null) {
          layer = L.geoJSON(lowerCut, {
            style: function(feature) {
              return {color: 'yellow' };
            }
          }).addTo(map);
          layers.push(layer);
        };
      });
    };
  });

I want the polygon split into 12 polygons. now it's just two(green & yellow).

I got this from this here

Example of what I am looking for

Best Answer

Update: See improved split method here: Splitting a polygon by multiple linestrings leaflet and turf.js

Code mentioned in the question was produced as kind of a proof of concept that polygons can be splitted with Turf.js library (see Client-Side Polygon split) It's not very robust and thoroughly tested.

Below is modified code which allows to split polygons multiple times with lines that have multiple points. Again it not robust and thoroughly tested, just a basic proof of concept.

At each step (split) the following layers and arrays are updated:

  • Layer drawnPolygons contains all polygons, split and unsplit
  • Layer drawnLines contains all lines used for splitting
  • Array polygons contains all polygons that correspond to drawnPolygons layer

The main part of the code:

function cutPolygon(polygon, line, direction, id) {
  var j;
  var polyCoords = [];
  var cutPolyGeoms = [];
  var retVal = null;

  if ((polygon.type != 'Polygon') || (line.type != 'LineString')) return retVal;

  var intersectPoints = turf.lineIntersect(polygon, line);
  var nPoints = intersectPoints.features.length;
  if ((nPoints == 0) || ((nPoints % 2) != 0)) return retVal;

  var offsetLine = turf.lineOffset(line, (0.01 * direction), {units: 'kilometers'});

  for (j = 0; j < line.coordinates.length; j++) {
    polyCoords.push(line.coordinates[j]);
  }
   for (j = (offsetLine.geometry.coordinates.length - 1); j >= 0; j--) {
    polyCoords.push(offsetLine.geometry.coordinates[j]);
  }
  polyCoords.push(line.coordinates[0]);
  var thickLineString = turf.lineString(polyCoords);
  var thickLinePolygon = turf.lineToPolygon(thickLineString);   

  var clipped = turf.difference(polygon, thickLinePolygon);  
  for (j = 0; j < clipped.geometry.coordinates.length; j++) {
    var polyg = turf.polygon(clipped.geometry.coordinates[j]);
    var overlap = turf.lineOverlap(polyg, line, {tolerance: 0.005});
    if (overlap.features.length > 0) {
      cutPolyGeoms.push(polyg.geometry.coordinates);
    };
  };

  if (cutPolyGeoms.length == 1)
    retVal = turf.polygon(cutPolyGeoms[0], {id: id});
  else if (cutPolyGeoms.length > 1) {
    retVal = turf.multiPolygon(cutPolyGeoms, {id: id});
  }

  return retVal;
};

var polygons = [];

map.on(L.Draw.Event.CREATED, function (event) {
  var layer = event.layer;

  var geojson = layer.toGeoJSON();
  var geom = turf.getGeom(geojson);

  if (geom.type == 'Polygon') {
    polygons.push(geom);
    drawnPolygons.addLayer(layer);
    }
  else if (geom.type == 'LineString') {
    var line = geom;
    drawnLines.addLayer(layer);
    drawnPolygons.clearLayers();
    var newPolygons = [];
    polygons.forEach(function (polygon, index) {
      var cutDone = false;
      var layer;
      var upperCut = cutPolygon(polygon, line, 1, 'upper');
      var lowerCut = cutPolygon(polygon, line, -1, 'lower');
      if ((upperCut != null) && (lowerCut != null)) {
        layer = L.geoJSON(upperCut, {
          style: function(feature) {
            return {color: 'green' };
          }
        }).addTo(drawnPolygons);   
        layer = L.geoJSON(lowerCut, {
          style: function(feature) {
            return {color: 'yellow' };
          }
        }).addTo(drawnPolygons);
        cutDone = true;
      };
      if (cutDone) {
        newPolygons.push(upperCut.geometry);
        newPolygons.push(lowerCut.geometry);
        }
      else {
        newPolygons.push(polygon);
        layer = L.geoJSON(polygon, {
          style: function(feature) {
            return {color: '#3388ff' };
          }
        }).addTo(drawnPolygons);   
      }
    });
    polygons = newPolygons;
  };
});

Working JSFiddle is available at https://jsfiddle.net/TomazicM/x1a4d9ho/.