Leaflet Turf JS – Fixing Polygon with Negative Offset Missing Parts

leafletleaflet-pluginspolygonturf

I'm new in Turf JS and I have a polygon with a negative offset like:

const coords= [
  [25.085416706126708,55.1561039686203],
  [25.085351117322578,55.15612542629241],
  [25.08549687017286,55.15618711709976],
  [25.08557703416652,55.15636146068573],
  [25.085567317321594,55.15644192695618],
  [25.085411847697962,55.15658676624298],
  [25.085487153321573,55.15664041042328],
  [25.085615901538485,55.156659185886376],
  [25.085598897064752,55.15631586313248],
  [25.085535737570268,55.15617907047272]
];

const polygon = L.polygon(coords, { fillColor: 'orange', fillOpacity: 0.5, weight: 20, color: 'white', opacity: 0.5, fill: true});
const buffer = turf.buffer(polygon.toGeoJSON(), -3, { units: 'meters' });

const buffered = L.geoJSON(buffer, {
  color: 'orange',
  weight: 15,
  lineJoin: "miter",
  //lineCap: "round",
  opacity: 0.5,
  fillOpacity: 0
}).addTo(map);

    map.on('zoomend', function (e) {
       map.eachLayer(function (layer) {
          layer.setStyle({ weight: map.getZoom() > 16 ? (75 / Math.pow(2, 19 - map.getZoom())) : 15 });
          layer.bringToFront();
       });
    }

but is missing some areas when drawn on the map, like:

enter image description here

The real polygon shape, without offset (or offset set to 0) is

enter image description here

If I'm increasing the weight to the polygon with offset, a larger area will be missing and the same thing happen for smaller polygons with closer lines.

In the jsfiddle, the orange offset polygon should go around the red one (on the inside) but is missing some small areas. It should fill that small area from inside, even if the offset polygon borders are overlapping

Edit: I'm trying to achieve the just darker line from a polygon that has filling
enter image description here

For example, if I have a polygon with 40m border and filling color, 20m of the border will overlap the filling color. That area is what I'm trying to achieve. I'm changing the border width when the zoom changes to keep the same 40m. Is there a way to draw it using Turf JS?

Best Answer

Basic principle to create polygon with custom inner and outer border, where each part of polygon (inner border, outer border, interior) has it's own style, using turf.js library, would be:

  • Create polygon buffer with positive offset with turf.buffer method, then create difference between buffer and polygon with turf.difference method. Resulting polygon is outer border.
  • Create polygon buffer with negative offset with turf.buffer method, then create difference between polygon and buffer turf.difference method. Resulting polygon is inner border. If negative buffer polygon is empty, whole polygon gets role of inner border.
  • Use buffer with negative offset as polygon interior. If negative buffer is empty, polygon has no interior.

Polygon with custom border is then feature group consisting of those three polygons.

Code could then look something like this:

function polygonWithCustomBorder(coords, polyStyle, outerBorderStyle, innerBorderStyle) {
  var polygonGroup = L.layerGroup({
    interactive: true
  });

  var polygon = L.polygon(coords, {
    stroke: false,
    fill: true,
    fillOpacity: 0,
  });
  polygon.addTo(polygonGroup);
  
  var polyGeoJSON = polygon.toGeoJSON();
  
  var outerBuffer = turf.buffer(polyGeoJSON, outerBorderStyle.width, {units: 'meters'});
  var outerDiff = turf.difference(outerBuffer, polyGeoJSON);     
  var outerBorder = L.geoJSON(outerDiff, {
    weight: 0,
    lineJoin: "miter",
    fillOpacity: outerBorderStyle.opacity,
    fillColor: outerBorderStyle.color
  }).addTo(polygonGroup);
  
  var innerBuffer = turf.buffer(polyGeoJSON, -innerBorderStyle.width, {units: 'meters'});
  if (innerBuffer)
    var innerDiff = turf.difference(polyGeoJSON, innerBuffer);
  else {
    var innerDiff = polyGeoJSON;
  }
  var innerBorder = L.geoJSON(innerDiff, {
    weight: 0,
    lineJoin: "miter",
    fillOpacity: innerBorderStyle.opacity,
    fillColor: innerBorderStyle.color
  }).addTo(polygonGroup);

  if (innerBuffer) {
    var innerPolygon = L.geoJSON(innerBuffer, {
      weight: 0,
      lineJoin: "miter",
      fillOpacity: polyStyle.fillOpacity,
      fillColor: polyStyle.fillColor
    }).addTo(polygonGroup);
  }

  return polygonGroup;
}

An example of creating polygon with the above function:

const coords = [
  [25.085416706126708,55.1561039686203],
  [25.085351117322578,55.15612542629241],
  [25.08549687017286,55.15618711709976],
  [25.08557703416652,55.15636146068573],
  [25.085567317321594,55.15644192695618],
  [25.085411847697962,55.15658676624298],
  [25.085487153321573,55.15664041042328],
  [25.085615901538485,55.156659185886376],
  [25.085598897064752,55.15631586313248],
  [25.085535737570268,55.15617907047272]
];   

var polygon = polygonWithCustomBorder(
  coords,
  {fillOpacity: 0.2, fillColor: 'red'},
  {width: 5, color: 'blue', opacity: 0.5},
  {width: 3, color: 'red', opacity: 0.5}
);
polygon.addTo(map);

Here's how it looks like:

enter image description here