OpenLayer4——贝塞尔曲线插值算法

主要涉及2阶贝塞尔曲线的应用,为了方便维护,牺牲了一部分性能,如果有能力可以进行调优。

功能描述:将尖锐的角变成圆角

使用场景:

比如,你手头有3个点,(0,0),(5,0),(5,5),连在一起显然是一条折线,通过贝塞尔曲线算法,可以算出(0,0)到(5,5)中间的折线,这些折线连起来看起来像是一个曲线。

当然,上面3个点,如果从(0,0)直接开始画曲线,画到(5,5),最终就变成了1/4圆,这可能不是很多人想要的,需求通常只是想要一个圆角,而不是一个圆,

因此,下面算法,允许从(0,0)到(5,0)选一个点作为起始点,开始绘制圆角。

 

JavaScript版本

/**
 * 贝塞尔曲线插值点
 */
function BezierLine(){
    //阶乘
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else if(num > 5){
            //减少递归
            return num * (num - 1) * (num - 2) * (num - 3) * (num - 4) * factorial(num - 5);
        }else {
            return num * factorial(num - 1);
        }
    }

    /**
     * 生成贝塞尔曲线插值点
     * @param n
     * @param arrPoints
     * @param res
     * @returns {Array}
     */
    function createBezierCurvePoints(n, arrPoints, res) {
        var Ptx = 0, Pty = 0;
        //曲线精细程度参数
        //这里写死0.2,取值越小,曲线越精细,产生5 (1/0.2)个计算结果,写0.01,则产生100个结果
        for (var t = 0; t < 1; t = t + 0.2) {
            Ptx = 0; Pty = 0;
            for (var i = 0; i <= n; i++) {
                Ptx += (factorial(n) / (factorial(i) * factorial(n - i))) * Math.pow((1 - t), n - i) * Math.pow(t, i) * arrPoints[i][0];
                Pty += (factorial(n) / (factorial(i) * factorial(n - i))) * Math.pow((1 - t), n - i) * Math.pow(t, i) * arrPoints[i][1];
            }
            res.push([Ptx, Pty]);
        }
    }

    /**
     *
     * p1到p2取n等分,取第m个点
     *
     * @param n,
     * @param m,
     * @param p1
     * @param p2
     */
    function dividedPoint(n, m, p1, p2) {
        return [(p2[0] - p1[0])/n * m + p1[0], (p2[1] - p1[1])/n * m + p1[1]]
    }


    /**
     * 因为实际业务场景中,往往不需要从最开始的点绘制曲线,只需要把角变得圆滑即可
     *
     * 比如:
     * 原始数据:(0,0),(5,0),(5,5)
     * 计算结果:(4,0),(5,0),(5,1)
     * 不需要从(0,0)点开始绘制曲线,只需要从(4,0)开始绘制曲线,结束于(5,1)点。
     *
     * 当dividedPart为5时,取其中的五等分点,即可满足上述需求
     * dividedPart值越大,平滑曲线越小,看起来越尖锐,
     */
    var dividedPart = 5;

    /**
     * 2阶贝塞尔曲线插值点计算
     * @param p1
     * @param p2
     * @param p3
     * @param res
     */
    function bezierCurvePoints3(p1, p2, p3, res) {
        //计算五分之4等分点
        var point1 = dividedPoint(dividedPart,dividedPart -1 , p1, p2);
        var point2 = p2;
        //计算五分之1等分点
        var point3 = dividedPoint(dividedPart,1, p2, p3);

        //开始点
        res.push(point1);
        //计算曲线坐标点
        createBezierCurvePoints(2, [point1, point2, point3], res);
        //结束点
        res.push(point3);
    }

    return {
        getPolygonLine: function (Polygon) {
            var line = Polygon[0];
            if(!Array.isArray(line)){
                throw '地图数据不是数组!';
            } else if(line.length < 3){
                throw '图形点的数量不允许小于3:' + line;
            } else {
                var n = line.length - 2, res = [];
                for (var i = 0; i < n; i++) {
                    bezierCurvePoints3(line[i], line[i + 1], line[i + 2], res);
                }
                //倒数第一个点
                bezierCurvePoints3(line[n], line[0], line[1], res);
                return res;
            }
        }
    }
}

 

 

Java版本

import com.sun.prism.impl.shape.ShapeUtil;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author Mr.css
 * @Date 2020/11/5
 */
public class BezierLine {
    private static Logger log = LogManager.getLogger(ShapeUtil.class);

    private static GeometryFactory geometryFactory = new GeometryFactory();

    private static int dividedPart = 5;

    /**
     * 阶乘
     *
     * @param n 阶层数量
     * @return 阶乘结果
     */
    private static double factorial(int n) {
        if (n == 2) {
            return n * (n - 1);
        } else if (n <= 1) {
            return 1;
        } else if (n > 5) {
            return n * (n - 1) * (n - 2) * (n - 3) * (n - 4) * factorial(n - 5);
        } else {
            return n * factorial(n - 1);
        }
    }

    /**
     * 生成n阶贝塞尔曲线插值点
     *
     * @param n         阶乘数
     * @param arrPoints 数组
     * @param res       结果
     */
    private static void createBezierCurvePoints(int n, Coordinate[] arrPoints, List<Coordinate> res) {
        double Ptx = 0, Pty = 0;
        //常量0.2,取值越小,曲线越精细,产生5(1/0.2)个计算结果
        for (double t = 0; t < 1; t = t + 0.2) {
            Ptx = 0;
            Pty = 0;
            for (int i = 0; i <= n; i++) {
                Ptx += (factorial(n) / (factorial(i) * factorial(n - i))) * Math.pow((1 - t), n - i) * Math.pow(t, i) * arrPoints[i].x;
                Pty += (factorial(n) / (factorial(i) * factorial(n - i))) * Math.pow((1 - t), n - i) * Math.pow(t, i) * arrPoints[i].y;
            }
            res.add(new Coordinate(Ptx, Pty));
        }
    }

    /**
     * p1到p2取n等分,取第m个点
     *
     * @param n  点p1 到 p2 做n等分,
     * @param m  取第m个等分点,
     * @param p1 点 p1
     * @param p2 点 p2
     */
    private static Coordinate dividedPoint(double n, double m, Coordinate p1, Coordinate p2) {
        return new Coordinate((p2.x - p1.x) / n * m + p1.x, (p2.y - p1.y) / n * m + p1.y);
    }

    /**
     * 2阶贝塞尔曲线插值点计算
     *
     * @param p1
     * @param p2
     * @param p3
     * @param res
     */
    private static List<Coordinate> bezierCurvePoints3(Coordinate p1, Coordinate p2, Coordinate p3, List<Coordinate> res) {
        //计算五分之4等分点
        Coordinate point1 = dividedPoint(dividedPart, dividedPart - 1, p1, p2);
        Coordinate point2 = p2;
        //计算五分之1等分点
        Coordinate point3 = dividedPoint(dividedPart, 1, p2, p3);

        //开始点
        res.add(point1);
        //计算曲线坐标点
        createBezierCurvePoints(2, new Coordinate[]{point1, point2, point3}, res);
        //结束点
        res.add(point3);
        return res;
    }

    /**
     * 计算一个多边形平滑之后的曲线
     *
//     * @param polygon 多边形
     * @return 数组
     */
    public static Coordinate[] getLine(Coordinate[] line ) {
        if (line.length < 3) {
            log.error("图形点的数量不允许小于3:" + Arrays.toString(line));
            return line;
        } else {
            int n = line.length - 2;
            List<Coordinate> list = new ArrayList<>(line.length * 5);
            for (int i = 0; i < n; i++) {
                bezierCurvePoints3(line[i], line[i + 1], line[i + 2], list);
            }
            //倒数第一个点
            bezierCurvePoints3(line[n], line[0], line[1], list);
            list.add(list.get(0));
            return list.toArray(new Coordinate[]{});
        }
    }

    public static void main(String[] args) {
        Coordinate[] line = new Coordinate[]{
            new Coordinate(1, 1),
            new Coordinate(1, 5),
            new Coordinate(5, 5),
            new Coordinate(5, 1),
            new Coordinate(1, 1)
        };
        Coordinate[] res = getLine(line);
        Polygon polygon = geometryFactory.createPolygon(line);
        System.out.println(Arrays.toString(res));
    }
}

 

posted on 2022-05-20 10:57  疯狂的妞妞  阅读(465)  评论(0编辑  收藏  举报

导航