diff --git a/themes/intotheeast/js/maplibre-utils.js b/themes/intotheeast/js/maplibre-utils.js index fe53b40..c0b3511 100644 --- a/themes/intotheeast/js/maplibre-utils.js +++ b/themes/intotheeast/js/maplibre-utils.js @@ -10,28 +10,56 @@ } /* - * Catmull-Rom spline through waypoints → dense interpolated coords. - * Produces a smooth curve that passes through every entry dot. - * steps: interpolated points per segment (16 is plenty for daily entries). + * Centripetal Catmull-Rom spline (α=0.5) through waypoints → dense coords. + * Parameterising by √chord-length prevents the large bow that uniform + * parameterisation produces when consecutive points are very close together + * but their neighbours are far away (e.g. two entries both in Berlin). */ function catmullRomSpline(coords, steps) { if (coords.length < 2) return coords; steps = steps || 16; - var out = []; + var alpha = 0.5; + var out = []; - for (var i = 0; i < coords.length - 1; i++) { - var p0 = coords[Math.max(i - 1, 0)]; - var p1 = coords[i]; - var p2 = coords[i + 1]; - var p3 = coords[Math.min(i + 2, coords.length - 1)]; + /* Phantom endpoints via reflection so the spline reaches the first and last real points */ + var ext = [ + [2*coords[0][0] - coords[1][0], 2*coords[0][1] - coords[1][1]] + ].concat(coords).concat([ + [2*coords[coords.length-1][0] - coords[coords.length-2][0], + 2*coords[coords.length-1][1] - coords[coords.length-2][1]] + ]); + + function segT(a, b) { + var dx = b[0]-a[0], dy = b[1]-a[1]; + return Math.pow(Math.max(Math.sqrt(dx*dx + dy*dy), 1e-10), alpha); + } + + for (var i = 1; i < ext.length - 2; i++) { + var p0 = ext[i-1], p1 = ext[i], p2 = ext[i+1], p3 = ext[i+2]; + + var t0 = 0; + var t1 = t0 + segT(p0, p1); + var t2 = t1 + segT(p1, p2); + var t3 = t2 + segT(p2, p3); for (var s = 0; s < steps; s++) { - var t = s / steps; - var t2 = t * t; - var t3 = t2 * t; + var t = t1 + (t2 - t1) * s / steps; + + var a1x = (t1-t)/(t1-t0)*p0[0] + (t-t0)/(t1-t0)*p1[0]; + var a1y = (t1-t)/(t1-t0)*p0[1] + (t-t0)/(t1-t0)*p1[1]; + var a2x = (t2-t)/(t2-t1)*p1[0] + (t-t1)/(t2-t1)*p2[0]; + var a2y = (t2-t)/(t2-t1)*p1[1] + (t-t1)/(t2-t1)*p2[1]; + var a3x = (t3-t)/(t3-t2)*p2[0] + (t-t2)/(t3-t2)*p3[0]; + var a3y = (t3-t)/(t3-t2)*p2[1] + (t-t2)/(t3-t2)*p3[1]; + + var b1x = (t2-t)/(t2-t0)*a1x + (t-t0)/(t2-t0)*a2x; + var b1y = (t2-t)/(t2-t0)*a1y + (t-t0)/(t2-t0)*a2y; + var b2x = (t3-t)/(t3-t1)*a2x + (t-t1)/(t3-t1)*a3x; + var b2y = (t3-t)/(t3-t1)*a2y + (t-t1)/(t3-t1)*a3y; + out.push([ - 0.5 * ((2*p1[0]) + (-p0[0]+p2[0])*t + (2*p0[0]-5*p1[0]+4*p2[0]-p3[0])*t2 + (-p0[0]+3*p1[0]-3*p2[0]+p3[0])*t3), - 0.5 * ((2*p1[1]) + (-p0[1]+p2[1])*t + (2*p0[1]-5*p1[1]+4*p2[1]-p3[1])*t2 + (-p0[1]+3*p1[1]-3*p2[1]+p3[1])*t3) + (t2-t)/(t2-t1)*b1x + (t-t1)/(t2-t1)*b2x, + (t2-t)/(t2-t1)*b1y + (t-t1)/(t2-t1)*b2y ]); } }