问题
With Leaflet, I'm trying to render a lot of points (+ 10000) as a GeoJSON feature to improve performance. In order to achieve better results, I found this answer instructing on how to extend Leaflet's circleMarker to change it's shape like so:
L.Canvas.include({
_updateMarkerPin: function(layer) {
if (!this._drawing || layer._empty()) {
return
}
var p = layer._point,
ctx = this._ctx,
r = layer._radius
this._drawnLayers[layer._leaflet_id] = layer
ctx.beginPath()
ctx.moveTo(p.x, p.y)
ctx.lineTo(p.x - 0.58 * r, p.y - r)
ctx.arc(p.x, p.y - 2 * r, r, -Math.PI * 1.161, Math.PI * 0.161)
ctx.closePath()
this._fillStroke(ctx, layer)
},
})
const MarkerPin = L.CircleMarker.extend({
_updatePath: function() {
this._renderer._updateMarkerPin(this)
},
})
This is my MarkerPin Shape:
MarkerPin is then used in L.GeoJSON's pointToLayer option as follows:
const myPointToLayer = (feature, latlng) => {
var markerParams = {
radius: 16,
stroke: true,
weight: 2,
opacity: 0.4,
fillOpacity: 0.9,
}
return new MarkerPin(latlng, markerParams)
}
const myOnEachFeature = (feature, layer) => {
layer.bindPopup('Clicked me!')
}
L.geoJSON(data, {
pointToLayer:myPointToLayer,
onEachFeature: myOnEachFeature,
}).addTo(map);
So far so good. All the data gets rendered properly.
My problem is: the click area isn't updated according to my new shape, it stays like the circleMarker's one (because that's what I've extended to create my pin) illustrated by the red circle:
Other Leaflet elements, like polygons, have their click area match their shape. That being said, is it possible to change the click area of my MarkerPin to match my shape (even if I'm extending circleMarker)?
Even if it's not possible to change the shape of the click area, I'd be satisfied to being able to move it up a bit like the image below:
Or even, is there a better approach to render several markers in GeoJSON with a custom icon and handle their popups with Leaflet?
Edit
For completeness, this is what I'm using (thanks to https://stackoverflow.com/a/56072877/11064013), which uses the whole pin area:
const MarkerPin = L.CircleMarker.extend({
_updatePath: function() {
this._renderer._updateMarkerPin(this)
},
_containsPoint: function(p) {
let r = this._radius
let insideCircle =
p.add([0, r * 2]).distanceTo(this._point) <= r + this._clickTolerance()
let a = this._point,
b = a.subtract([0.58 * r, r]),
c = a.subtract([-0.58 * r, r])
let insideTriangle = true
let ap_x = p.x - a.x
let ap_y = p.y - a.y
let p_ab = (b.x - a.x) * ap_y - (b.y - a.y) * ap_x > 0
let p_ac = (c.x - a.x) * ap_y - (c.y - a.y) * ap_x > 0
let p_bc = (c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x) > 0
if (p_ac === p_ab) {
insideTriangle = false
}
if (p_bc !== p_ab) {
insideTriangle = false
}
return insideTriangle || insideCircle
},
})
回答1:
Click detection in vector layers rendered through a L.Canvas depends on the _containsPoint private method of each vector layers. See the implementation on CircleMarker or on Polyline.
Each time the user clicks on a L.Canvas renderer, the renderer loops through all the layers in it, and asks them "the user has done something in pixel p - is this yours"? The call to _containsPoint has to answer that.
So you'll have to play around with implementing _containsPoint for your MarkerPin class, maybe something like e.g.
const MarkerPin = L.CircleMarker.extend({
_updatePath: function() {
this._renderer._updateMarkerPin(this)
},
_containsPoint: function(p) {
return L.CircleMarker.prototype._containsPoint.call(this, p.subtract([0, 10]));
}
})
That should shift the click area up or down. If you want a better shape, you'll have to provide a proper implementation of a function that returns whether a point in inside the shape or not.
来源:https://stackoverflow.com/questions/56067809/markers-extended-from-circlemarker-how-to-change-the-click-area