传单中多边形显示的挑战

Challenge for polygon display within leaflet

我们在传单(最新版本)中的多边形显示方面遇到了特定的设计挑战。

我们有多边形,这些多边形呈现​​为实心边框和半透明背景。 我们正在寻找一种方法来绘制实线边界线以及更宽的“内联”边界并且没有背景。

Note: the question is for polygons not rectangular. The below image and code is just for example.

有什么办法可以实现吗?

var polygon = L.polygon([
  [ 51.72872938200587, -2.415618896484375 ],
  [ 51.72872938200587, -2.080535888671875 ],
  [ 51.901918172561714, -2.080535888671875 ],
  [ 51.901918172561714, -2.415618896484375 ],
  [ 51.72872938200587, -2.415618896484375 ]

],{
 color:'#2F538F',
 fillOpacity: 0.9,
 fillColor: '#BFBFBF',
}).addTo(map);

使用Recatngle/Polygon方法。

// define rectangle geographical bounds
var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
// create an orange rectangle
L.rectangle(bounds, {}).addTo(map);

使用选项在线条上获得所需的效果。选项继承自 polyline options

您可以调整 coloropacityfillfillColorfillOpacityfillRule 以获得所需的效果行

这可以通过利用 leaftlet 的 class extension 系统来实现。

首先,可以参考传单的 class diagram 来确定需要扩展的位置。作为一般规则,首先尝试将 classes 扩展到根,并且更喜欢 L.Class.extend 而不是 L.Class.include.

工作解决方案:

Codesandbox

一种方法是挂接到渲染过程。在下面的示例中,L.Canvas 被扩展为自定义 L.Canvas.WithExtraStyles class(传单的插件构建 guidelines). The custom Renderer 然后提供给 map.

在这种方法中,请注意可以使用 extraStyles 配置提供多个边框和填充(插图和开头)。

extraStyle 自定义 属性 接受 PathOptions 的数组。加上一个额外的 inset,其值可以是正数或负数的像素,代表 offset 形成主要几何图形的边界。 inset 的负值会将边界置于原始多边形之外。

在实施此类定制时,必须特别注意确保传单未将添加的定制视为单独的几何形状。否则交互式功能,例如Polygon Edit 或 Leaflet Draw 将出现意外行为

// CanvasWithExtraStyles.js
// First step is to provide a special renderer which accept configuration for extra borders.
// Here L.Canvas is extended using Leaflet's class system
const styleProperties = ['stroke', 'color', 'weight', 'opacity', 'fill', 'fillColor', 'fillOpacity'];

/*
 * @class Polygon.MultiStyle
 * @aka L.Polygon.MultiStyle
 * @inherits L.Polygon
 */
L.Canvas.WithExtraStyles = L.Canvas.extend({
  _updatePoly: function(layer, closed) {
    const centerCoord = layer.getCenter();
    const center = this._map.latLngToLayerPoint(centerCoord);
    const originalParts = layer._parts.slice();

    // Draw extra styles
    if (Array.isArray(layer.options.extraStyles)) {
      const originalStyleProperties = styleProperties.reduce(
        (acc, cur) => ({ ...acc, [cur]: layer.options[cur] }),
        {}
      );
      const cx = center.x;
      const cy = center.y;

      for (let eS of layer.options.extraStyles) {
        const i = eS.inset || 0;

        // For now, the algo doesn't support MultiPolygon
        // To have it support MultiPolygon, find centroid
        // of each MultiPolygon and perform the following
        layer._parts[0] = layer._parts[0].map(p => {
          return {
            x: p.x < cx ? p.x + i : p.x - i,
            y: p.y < cy ? p.y + i : p.y - i
          };
        });

        //Object.keys(eS).map(k => layer.options[k] = eS[k]);
        Object.keys(eS).map(k => (layer.options[k] = eS[k]));
        L.Canvas.prototype._updatePoly.call(this, layer, closed);
      }

      // Resetting original conf
      layer._parts = originalParts;
      Object.assign(layer.options, originalStyleProperties);
    }

    L.Canvas.prototype._updatePoly.call(this, layer, closed);
  }
});
// Leaflet's conventions to also provide factory methods for classes
L.Canvas.withExtraStyles = function(options) {
  return new L.Canvas.WithExtraStyles(options);
};


// --------------------------------------------------------------

// map.js
const map = L.map("map", {
  center: [52.5145206, 13.3499977],
  zoom: 18,
  renderer: new L.Canvas.WithExtraStyles()
});

new L.tileLayer(
  "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png",
  {
    attribution: `attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attribution">CARTO</a>`,
    detectRetina: true
  }
).addTo(map);

// Map center
const { x, y } = map.getSize();

// Left Polygon
const polyStyle1 = {
  color: '#2f528f',
  extraStyles: [
    {
      color: 'transparent',
      weight: 10,
      fillColor: '#d9d9d9'
    }
  ]
};

// Sudo coordinates are generated form map container pixels
const polygonCoords1 = [
  [0, 10],
  [300, 10],
  [300, 310],
  [0, 310]
].map(point => map.containerPointToLatLng(point));
const polygon1 = new L.Polygon(polygonCoords1, polyStyle1);
polygon1.addTo(map);

// Right Polygon
const polyStyle2 = {
  fillColor: "transparent",
  color: '#2f528f',
  extraStyles: [
    {
      inset: 6,
      color: '#d9d9d9',
      weight: 10
    }
  ]
};

const polygonCoords2 = [
  [340, 10],
  [640, 10],
  [640, 310],
  [340, 310]
].map(point => map.containerPointToLatLng(point));
const polygon2 = new L.Polygon(polygonCoords2, polyStyle2);
polygon2.addTo(map);
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>

<div id="map" style="width: 100vw; height: 100vw">0012</div>

理想解:

  • 将插件实现为单独的 npm 模块。
  • 尝试扩展或挂接到 Renderer 本身,而不是单独扩展 L.Canvas 和 L.SVG。
  • 将自定义化挂钩到基础 class Path 而不是单独的形状:多边形、折线或圆形。