如何使用Leaflet.draw选择多个标记?

11

背景:

我创建了一个地图,并在其中随机添加了大约300个标记。我可以通过在弹出窗口中点击链接来“选择”标记并激活选择以显示数据。我还使用了Leaflet.draw插件来绘制圆形、矩形和自定义形状,我想使用它来“选择”一些标记。

问题:

如何获取落在绘制的Leaflet.draw形状内的标记的leaflet标记对象,以便我可以编辑它们?我似乎无法进行选择,要么不选择任何标记,要么选择所有标记。

代码片段,已剥离不必要的代码:

const drawControl = new L.Control.Draw({
    draw: {
        marker   : false,
        polygon  : true,
        polyline : false,
        rectangle: true,
        circle   : {
            metric: 'metric'
        }
    },
    edit: false
});

const map = L.map('map', {
    layers: [streets, light]
}).setView([CONFIG.MAP.LATITUDE, CONFIG.MAP.LONGITUDE], CONFIG.MAP.ZOOMLEVEL)

map.addControl(drawControl);

map.on(L.Draw.Event.DRAWSTOP, e => {

    const hello = e.target;

    console.log(hello);
    e.target.eachLayer(layer => {
        if (layer.options.icon) {
            console.log(layer);
        }
    });

});
3个回答

18

大部分你想做的事情都可以通过使用Leaflet的实用方法轻松完成。如果您要在像L.Polygon这样的复杂形状上执行此操作,则需要使用TurfJS之类的东西。

对于L.Circle,您需要计算圆心与半径之间的距离并进行比较:

var marker = new L.Marker(...),
    circle = new L.Circle(...);

var contains = circle.getLatLng().distanceTo(marker.getLatLng()) < circle.getRadius();
< p > 对于 < code > L.Rectangle ,您需要获取其边界对象并使用contains方法:
var marker = new L.Marker(...),
    rectangle = new L.Rectangle(...);

var contains = rectangle.getBounds().contains(marker.getLatLng());

对于复杂的多边形,我会使用Turf,但还有其他的库和插件可用。这里是一个使用Turf的inside方法的示例。它需要一个GeoJSON点和多边形要素作为参数,因此要注意转换:

var marker = new L.Marker(...),
    polygon = new L.Polygon(...);

var contains = turf.inside(marker.toGeoJSON(), polygon.toGeoJSON());

您可以将它们包装成每个相应类的便捷方法:

L.Polygon.include({
    contains: function (latLng) {
        return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON());
    } 
});

L.Rectangle.include({
    contains: function (latLng) {
        return this.getBounds().contains(latLng);
    }
});

L.Circle.include({
    contains: function (latLng) {
        return this.getLatLng().distanceTo(latLng) < this.getRadius();
    }
});

var marker = new L.Marker(...),
    polygon = new L.Polygon(...),
    rectangle = new L.Rectangle(...),
    circle = new L.Circle(...);

polygon.contains(marker.getLatLng());
rectangle.contains(marker.getLatLng());
circle.contains(marker.getLatLng());

请确认以下翻译是否符合要求:

请注意,如果您实现了多边形方法,则无需使用矩形方法。由于矩形是从多边形扩展的,它将继承该方法。我留下它是为了完整性。

现在迭代标记并进行比较很容易:

map.on(L.Draw.Event.CREATED, function (e) {
    markers.eachLayer(function (marker) {
        if (!e.layer.contains(marker.getLatLng())) {
            marker.remove();
        }
    });
});

Hope that helps, here's a working snippet:

var map = new L.Map('leaflet', {
    'center': [0, 0],
    'zoom': 0
});

var markers = new L.LayerGroup().addTo(map);

for (var i = 0; i < 300; i++) {
    var marker = new L.Marker([
        (Math.random() * (90 - -90) + -90).toFixed(5) * 1,
        (Math.random() * (180 - -180) + -180).toFixed(5) * 1
    ]).addTo(markers);
}

new L.Control.Draw({
    draw: {
        marker   : false,
        polygon  : true,
        polyline : false,
        rectangle: true,
        circle   : {
            metric: 'metric'
        }
    },
    edit: false
}).addTo(map);

L.Polygon.include({
    contains: function (latLng) {
        return turf.inside(new L.Marker(latLng).toGeoJSON(), this.toGeoJSON());
    } 
});

L.Rectangle.include({
    contains: function (latLng) {
        return this.getBounds().contains(latLng);
    }
});

L.Circle.include({
    contains: function (latLng) {
        return this.getLatLng().distanceTo(latLng) < this.getRadius();
    }
});

map.on(L.Draw.Event.CREATED, function (e) {
    markers.eachLayer(function (marker) {
        if (!e.layer.contains(marker.getLatLng())) {
            marker.remove();
        }
    });
});
body {
    margin: 0;
}

html, body, #leaflet {
    height: 100%;
}
<!DOCTYPE html>
<html>
  <head>
    <title>Leaflet 1.0.3</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link type="text/css" rel="stylesheet" href="//unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
    <link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.css" />
  </head>
  <body>
    <div id="leaflet"></div>
    <script type="application/javascript" src="//unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
    <script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script>
    <script type="application/javascript" src="//unpkg.com/@turf/turf@latest/turf.min.js"></script>
  </body>
</html>


哇塞,我已经看了一整个早上了,让我试着将它转换到我的代码库中,非常感谢你迄今为止的帮助! - roberrrt-s
非常好,非常感谢您的帮助 :)。 - roberrrt-s
1
完全没有问题,不用谢,随时欢迎。这就是我们在这里的目的 ;) - iH8

3
感谢 @iH8 提供的精彩示例。我进一步采取了一些措施,以避免一些重复使用


markers.eachLayer(function (marker) {
    ...
}

我使用数组标记扩展了包装器的附加方法:


首先,我注意到LayerGroup有一个对象,其中包含所有标记的键值对。我只需使用该对象创建一个标记数组:

// In the on draw event
...
// Set an array containing all the markers
var markers = jsonToArray(layerGroup._layers); 
...

function jsonToArray(jsonObject) {
  var result = [];
  var keys = Object.keys(jsonObject);
  keys.forEach(function (key) {
    result.push(jsonObject[key]);
  });
  return result;
}

我接着使用具有修改后的contains()方法的封装:

  L.Rectangle.include({
    // Single marker case
    contains: function (marker) {
      return this.getBounds().contains(marker.getLatLng());
    },
    // Array of markers
    contains: function (markers) {
      var markersContained = [];
      markers.forEach(marker => {
        markersContained.push(this.getBounds().contains(marker.getLatLng()));
      })
      return markersContained;
    }
  });

  L.Circle.include({
    contains: function (marker) {
      return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
    },
    contains: function (markers) {
      var markersContained = [];
      markers.forEach(marker => {
        markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
      })
      return markersContained;
    }
  });

最后,在绘制事件中,我检查我的标记是否被包含在内:

  map.on(L.Draw.Event.CREATED, function (geometry) {
    // Set an array containing all the markers
    var markers = jsonToArray(layerGroup._layers);

    var result = geometry.layer.contains(markers);
    console.log('result => ', result);
  });

function jsonToArray(jsonObject) {
  var result = [];
  var keys = Object.keys(jsonObject);
  keys.forEach(function (key) {
    result.push(jsonObject[key]);
  });
  return result;
}

var map = new L.Map('leaflet', {
    'center': [0, 0],
    'zoom': 0
});

var layerGroup = new L.LayerGroup().addTo(map);

for (var i = 0; i < 10; i++) {
    var marker = new L.Marker([
        (Math.random() * (90 - -90) + -90).toFixed(5) * 1,
        (Math.random() * (180 - -180) + -180).toFixed(5) * 1
    ]).addTo(layerGroup);
}

new L.Control.Draw({
    draw: {
        marker   : false,
        polygon  : false,
        polyline : false,
        rectangle: true,
        circle   : {
            metric: 'metric'
        }
    },
    edit: false
}).addTo(map);

// Define contains() method for each geometry
L.Rectangle.include({
  contains: function (marker) {
    return this.getBounds().contains(marker.getLatLng());
  },
  contains: function (markers) {
    var markersContained = [];
    markers.forEach(marker => {
      markersContained.push(this.getBounds().contains(marker.getLatLng()));
    })
    return markersContained;
  }
});

L.Circle.include({
  contains: function (marker) {
    return this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius();
  },
  contains: function (markers) {
    var markersContained = [];
    markers.forEach(marker => {
      markersContained.push(this.getLatLng().distanceTo(marker.getLatLng()) < this.getRadius());
    })
    return markersContained;
  }
});

map.on(L.Draw.Event.CREATED, function (geometry) {
  // Set an array containing all the markers
  var markers = jsonToArray(layerGroup._layers);

  var result = geometry.layer.contains(markers);
  console.log('result => ', result);
});
body {
    margin: 0;
}

html, body, #leaflet {
    height: 100%;
}
<!DOCTYPE html>
<html>
  <head>
    <title>Leaflet 1.0.3</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link type="text/css" rel="stylesheet" href="//unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
    <link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.css" />
  </head>
  <body>
    <div id="leaflet"></div>
    <script type="application/javascript" src="//unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
    <script type="application/javascript" src="//cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw.js"></script>
  </body>
</html>


这非常有帮助,谢谢!你能建议一种方法让我在控制台中输出标记内容而不是 true/false 吗?理想情况下,我想选择地图上的一组标记并返回某种标记 ID。 - Josh
我已经很久没有在这个项目上工作了,但我猜你可以尝试记录标记本身? 老实说,我真的不太清楚了。 - Alex Beugnet
1
我最终解决了这个问题,如果对任何人有帮助,我在此处包含了我的代码。这将返回标记数据,而不是True和False: L.Rectangle.include({ contains:function(markers){ var markersContained = markers.filter(marker => { return this.getBounds().contains(marker.getLatLng())=== true; }) 返回markersContained; } }); - Josh

0

我使用了它:

L.Circle.include({
    contains: function (latLng) {
        return this.getLatLng().distanceTo(latLng) < this.getRadius();
    }
});

在圆边缘上但不在圆内的点也会被判断。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接