1

I'm a beginner in Leaflet JS and I have a polygon drawn on the map:

var coords= [
   [27.40839959582344,-82.40601726602196],
   [27.40859871554781,-82.40638191331237],
   [27.40834938302612,-82.40652231229888]
];

L.polygon(coords, { fillColor: 'blue', fillOpacity: 0.5, weight: 75 / Math.pow(2, 19 - map.getZoom()), color: 'red', opacity: 0.5, fill: true}).addTo(map);

and I'm changing the border weight every time I change the zoom level, to keep it to about 20 meters

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();
   });
}

JSFiddle

What is best to use in order to increase the outer part of the border, from 10 m to 15 m but to keep the inside part (that overlaps the fill color) the same size of 10? Can it work with drawControl when editing and deleting the polygon?

EDIT: enter image description here

In the image the polygon filling is red and the border(stroke) blue. Half of the border is overlapping the filling (becoming a purplish color). That is the inner part of the polygon stroke that should remain in place/the same size. The other half (that has the default blue color - drawn on the outside) is the one that should increase in size to the zone marked with the pen. I need to increase the width only for half of the stroke

4
  • Some effort went into answering your similar question that you now deleted and posted a new one, and this one is even less understandable, since there is no image. That's not how things should be done on GIS SE site.
    – TomazicM
    Jul 5 at 13:51
  • @TomazicM Sorry for that :( It's available again. The reason why I've switched to this question is because I tried to change the start approach
    – LAffair
    Jul 5 at 13:58
  • 1
    This question is definitely a lot cleaner since it deals with what you are trying to achieve, instead of dealing with how you decided to achieve it. Please add a picture to illustrate what you mean by inner and outer border. It's not clear what this could be from code above or from your JSFiddle.
    – TomazicM
    Jul 5 at 14:35
  • @TomazicM Edited my question
    – LAffair
    Jul 5 at 18:10

2 Answers 2

2

Since border dimensions are in map units, obvious candidate for custom border creation is turf.js library.

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, 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.

Up till here things are relatively simple, but if requirement is that such polygon can be created, edited and deleted via leaflet.draw plugin, things become quite complicated.

Code below is proof of concept that it can be done:

var polygonOutlines = L.featureGroup().addTo(map);

var deleteActive = false;
var deleteCandidates = [];

function checkDeleteRequest(polygonGroup) {
  if (!deleteActive) return;
  
  deleteCandidates.push(polygonGroup);
  polygonGroup.borderData.polygon.fire('click');
  map.removeLayer(polygonGroup);
}

function createCustomPolygonBorder(polygonGroup) {
  var outerBorderStyle = polygonGroup.borderData.outerBorderStyle;
  var innerBorderStyle = polygonGroup.borderData.innerBorderStyle;
  var innerPolygonStyle = polygonGroup.borderData.innerPolygonStyle;
  var polyGeoJSON = polygonGroup.borderData.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);
  if (polygonGroup.borderData.outerBorder) {
    polygonGroup.removeLayer(polygonGroup.borderData.outerBorder);
  }
  polygonGroup.borderData.outerBorder = outerBorder;
  
  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 (polygonGroup.borderData.innerBorder) {
    polygonGroup.removeLayer(polygonGroup.borderData.innerBorder);
  }
  polygonGroup.borderData.innerBorder = innerBorder;       

  if (polygonGroup.borderData.innerPolygon) {
    polygonGroup.removeLayer(polygonGroup.borderData.innerPolygon);
    polygonGroup.borderData.innerPolygon = null;
  }
  if (innerBuffer) {
    var innerPolygon = L.geoJSON(innerBuffer, {
      weight: 0,
      lineJoin: "miter",
      fillOpacity: innerPolygonStyle.fillOpacity,
      fillColor: innerPolygonStyle.fillColor
    }).addTo(polygonGroup);
    innerPolygon.on('click', function(evt) {
      checkDeleteRequest(polygonGroup);
    });
    polygonGroup.borderData.innerPolygon = innerPolygon;
  }
}

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

  var polygon = L.polygon(coords, {
    stroke: false,
    fill: true,
    fillOpacity: 0,
  });
  polygon.options.polygonGroup = polygonGroup;
  polygon.addTo(polygonOutlines);
  
  polygonGroup.borderData = {};
  polygonGroup.borderData.polygon = polygon;
  polygonGroup.borderData.latLngs = polygon.getLatLngs();
  polygonGroup.borderData.isModified = false;
  polygonGroup.borderData.outerBorderStyle = outerBorderStyle;
  polygonGroup.borderData.innerBorderStyle = innerBorderStyle;
  polygonGroup.borderData.innerPolygonStyle = polyStyle;
  
  createCustomPolygonBorder(polygonGroup);

  return polygonGroup;
}

var drawnItems = new L.featureGroup().addTo(map);
var drawControl = new L.Control.Draw({
  draw: {
    marker: false,
    polyline: false,
    rectangle: false,
    circle: false,
    circlemarker: false,
    polygon: {
      allowIntersection: false,
      showArea: true
    }
  },
  edit: {
    featureGroup: polygonOutlines,
    poly: {
      allowIntersection: false
    }
  }
});
map.addControl(drawControl);

map.on('draw:created', function (event) {
  var layer = event.layer;

  var geoJSON = turf.flip(layer.toGeoJSON());
  var coords = geoJSON.geometry.coordinates[0];
  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);
});

map.on('draw:editstart', function (event) {
  console.log('draw:editstart', event);
  polygonOutlines.setStyle({
    stroke: true,
  });
});
map.on('draw:editvertex', function (event) {
  var polygonGroup = event.poly.options.polygonGroup;
  polygonGroup.borderData.isModified = true;
  createCustomPolygonBorder(polygonGroup);
});
map.on('draw:editstop', function (event) {
  console.log('draw:editstop', event);
  polygonOutlines.eachLayer(function(layer) {
    var borderData = layer.options.polygonGroup.borderData;
    if (borderData.isModified) {
      borderData.isModified = false;
      createCustomPolygonBorder(layer.options.polygonGroup);
    }
  });
  polygonOutlines.setStyle({
    stroke: false,
  });
});
map.on('draw:edited', function (event) {
  var layers = event.layers;
  layers.eachLayer(function(layer) {
    var borderData = layer.options.polygonGroup.borderData;
    borderData.polygon = layer;
    borderData.isModified = false;
    borderData.latLngs = layer.getLatLngs();
  });
});

map.on('draw:deletestart', function (event) {
  deleteActive = true;
  polygonOutlines.setStyle({
    stroke: true,
    fill: true,
    dashArray: '8 3',
    lineCap: 'butt'
  });
});
map.on('draw:deletestop', function (event) {
  deleteActive = false;
  polygonOutlines.setStyle({
    stroke: false,
    fill: false,
    dashArray: null
  });
  deleteCandidates.forEach(function(layer) {
    layer.addTo(map);
  });
  deleteCandidates = [];
});
map.on('draw:deleted', function (event) {
  var layers = event.layers;
  layers.eachLayer(function(layer) {
    if (map.hasLayer(layer.options.polygonGroup)) {
      map.removeLayer(layer.options.polygonGroup);
    }
  });
  deleteCandidates = [];
});

Below is an example of direct polygon creation:

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);

Result looks like this:

enter image description here

Here is working JSFiddle: https://jsfiddle.net/TomazicM/p3hnk0tu/

1
1

Not a direct answer, but this could lead you into the right direction. One approach would be to patch the canvas and add your custom subclass of polygon. I have updated your JSFiddle. You would need to integrate your custom logic into the _fillStroke function patching the ctx.

    L.Canvas.include({
      _fillStroke: function (ctx, layer) {
        var options = layer.options;

        if (options.fill) {
          ctx.globalAlpha = options.fillOpacity;
          ctx.fillStyle = options.fillColor || options.color;

          ctx.fill(options.fillRule || 'evenodd');
        }

        if (options.stroke && options.weight !== 0) {
          if (ctx.setLineDash) {
            ctx.setLineDash(layer.options && layer.options._dashArray || []);
          }
          ctx.globalAlpha = options.opacity;
          ctx.lineWidth = options.weight;
          
          // Custom logic here
          ctx.lineWidth = 30;
          
          ctx.strokeStyle = options.color;
          ctx.lineCap = options.lineCap;
          ctx.lineJoin = options.lineJoin;
          ctx.stroke();
        }
      },
    });

   const myRenderer = L.canvas({
        padding: 0.5,
    });

const map = L.map('map').setView([27.40839959582344,-82.40601726602196], 16);
L.tileLayer(
  'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: `&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> Contributors`,
    maxZoom: 20,
}).addTo(map);

var coords= [
   [27.40839959582344,-82.40601726602196],
   [27.40859871554781,-82.40638191331237],
   [27.40834938302612,-82.40652231229888]
];

const customPolygon = L.Polygon.extend({
    _updatePaths: function () {
        if (this._postponeUpdatePaths) { return; }

        var layer;
        this._redrawBounds = null;
        for (var id in this._layers) {
            layer = this._layers[id];
      layer._fillStroke(this._ctx, layer);
            layer._update();
        }
        this._redraw();
    },
})

const polygon = new customPolygon(coords, { 
    fillColor: '#63e5ff', 
  fillOpacity: 0.7, 
  weight: 75 / Math.pow(2, 19 - map.getZoom()), 
  color: '#da2c43', 
  opacity: 0.7, 
  fill: true,
  renderer: myRenderer
 }).addTo(map);
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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