This is actually easier than you think if you use a custom line generator. Instead of adding the control points to the feature, you just add them during the computation of the path. The code looks like this:
feature.attr("d", function(d) {
var s, prev;
d.geometry.coordinates.forEach(function(c) {
var proj = map.latLngToLayerPoint(new L.LatLng(c[1], c[0]));
if(s) {
var length = Math.sqrt(Math.pow(proj.x - prev.x, 2), Math.pow(proj.y - prev.y, 2)),
midx = prev.x + (proj.x - prev.x) / 2,
midy = prev.y + (proj.y - prev.y) / 2 - length * 0.2 * (Math.abs(proj.x - prev.x) / length);
s += "Q" + midx + "," + midy + " " + proj.x + "," + proj.y;
} else {
s = "M" + proj.x + "," + proj.y;
}
prev = proj;
});
return s;
});
Let's go through it step by step. The main thing is that I'm keeping track of the coordinates of the previous point to be able to compute the control point. First, s
will be null and the else
branch is taken -- simply move to that point (the start point) without drawing a line. For all subsequent points, the actual computation takes place.
First, we compute the distance between the two points (previous and current), length
. Computing the x
coordinate of the control point is straightforward, as no offset is required. The y coordinate is a bit trickier -- the first part is the same, then the offset is added. The size of the offset is 20% of the length of the path here (to make wider arcs for longer paths), adjust as necessary. This needs to be multiplied by the cosine of the angle to the x
axis, but fortunately we don't need to compute the angle explicitly -- it is defined by the relation between the distance between the points and the difference in x
coordinates (the arc cosine of that angle). So we can just take that relation directly and multiply by it. As you want arcs to point up (i.e. negative y
offset), we're taking the absolute value of the x
coordinate differences. Without that, some of the control points would be pointing down.
Complete example here.