概述
本文分享一个二阶贝塞尔曲线曲线生成弧线的算法。
效果
实现
1. 封装方法
class ArcLine {
constructor(from, to, num = 100) {
this.from = from;
this.to = to;
this.num = num;
return this.getPointList();
}
getPointList() {
const { from, to } = this
const ctrlPoint = this.getOffsetPoint(from, to);
const points = this.create2PBezier(from, ctrlPoint, to)
return points
}
getOffsetPoint(start, end) {
const distance = this.getDistance(start, end) / 2; //除以3?
let angle, dX, dY;
const mp = [start[0], start[1]];
const deltaAngle = - Math.PI / 8; //偏移0.2弧度
if (start[0] != end[0] && start[1] != end[1]) { //斜率存在
const k = (end[1] - start[1]) / (end[0] - start[0]);
angle = Math.atan(k);
} else if (start[0] == end[0]) { //垂直线
angle = (start[1] <= end[1] ? 1 : -1) * Math.PI / 2;
} else { //水平线
angle = 0;
}
if (start[0] <= end[0]) {
angle -= deltaAngle;
dX = Math.round(Math.cos(angle) * distance);
dY = Math.round(Math.sin(angle) * distance);
mp[0] += dX;
mp[1] += dY;
} else {
angle += deltaAngle;
dX = Math.round(Math.cos(angle) * distance);
dY = Math.round(Math.sin(angle) * distance);
mp[0] -= dX;
mp[1] -= dY;
}
return mp;
}
getDistance(p1, p2) {
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
}
bezier2P(p0, p1, p2, t) {
const P0 = p0 * Math.pow(1 - t, 2);
const P1 = p1 * 2 * t * (1 - t);
const P2 = p2 * t * t;
return P0 + P1 + P2;
}
getBezierNowPoint2P(p0, p1, p2, num, tick) {
return {
x: this.bezier2P(p0[0], p1[0], p2[0], num * tick),
y: this.bezier2P(p0[1], p1[1], p2[1], num * tick),
};
}
create2PBezier(p0, p1, p2) {
const num = this.num
const t = 1 / (num - 1);
const points = [];
for (let i = 0; i < num; i++) {
const point = this.getBezierNowPoint2P(p0, p1, p2, i, t);
points.push([point.x, point.y]);
}
return points;
}
}
2. 前端调用
示例使用openlayers实现。
let vetSource = new ol.source.Vector({
features: [],
});
let vectorLayer = new ol.layer.Vector({
source: vetSource,
style: new ol.style.Style({
stroke: new ol.style.Stroke({
color: "#f00",
width: 2,
}),
}),
});
map.addLayer(vectorLayer)
function addAllLines() {
let features = [];
for (let i = 0; i < pointData.length; i++) {
const after = pointData[i];
const from = [101.797439042302, 36.5937248286007];
const to = [after.lon, after.lat].map(Number);
let points = new ArcLine(from, to);
points = points.map((p) => ol.proj.fromLonLat(p));
lineData.push(points);
features.push(
new ol.Feature({
geometry: new ol.geom.LineString(points),
})
);
}
vetSource.addFeatures(features);
map.getView().animate({
center: [12474607.173951693, 4278483.982819865],
zoom: 3.8,
});
}