在 Leaflet.js 中实现爬行前进 (CLA) 到任务区域搜索模式转换

问题描述 投票:0回答:1

我需要使用 Leaflet.js 在任务区域(正方形、矩形和通用多边形)上绘制蠕动线(CLA)图案。 CLA 图案包括平行线,其直线段比要搜索区域的总宽度短。

这是 CLA 模式的示例:

搜索模式选项包括:

  • 轨道间距(各平行线段之间的距离) 自动将距边缘的距离设置为该字段中值的 1/2
  • 距边缘的距离
  • 初始标题 代表图案第一条腿的方向(基于真北)
  • 第一个转弯方向(左)
  • 开始搜索点(CSP) 它将被自动选择为由任务区域形状表示中的第一个顶点定义的角内,其中它与每条边的距离是与边缘的距离
  • 中的值

这是我写的相关代码:

<!DOCTYPE html>
<html>
<head>
    <title>Leaflet Creeping Line Ahead Pattern</title>
    <!-- Include Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
    <!-- Include Leaflet JavaScript -->
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <style>
        /* Set the size of the map */
        #map {
            height: 500px;
            width: 100%;
        }
    </style>
</head>
<body>
    <h2>Leaflet Creeping Line Ahead Pattern</h2>
    <!-- Create a div element to hold the map -->
    <div id="map"></div>

    <script>
        // Initialize the map and set its view to a given location and zoom level
        var map = L.map('map').setView([9.5415, 35.2651], 14);

        // Add an OpenStreetMap layer to the map
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);

        // Define the coordinates of the rectangle
        const rectangleCoords = [
            [9.531347, 35.25444], // top-left corner (latitude, longitude)
            [9.552678, 35.25444], // top-right corner (latitude, longitude)
            [9.552678, 35.27577], // bottom-right corner (latitude, longitude)
            [9.531347, 35.27577]  // bottom-left corner (latitude, longitude)
        ];

        // Create a polygon (rectangle) using the provided coordinates
        const rectangle = L.polygon(rectangleCoords, {
            color: 'blue', // Color of the rectangle border
            weight: 3, // Thickness of the rectangle border
            fillColor: 'blue', // Fill color of the rectangle
            fillOpacity: 0.2 // Opacity of the fill color
        }).addTo(map);

        // Function to draw the creeping line ahead pattern
        function drawCLA(bounds, start, initialHeading, firstTurn, trackSpacing, distanceFromEdge) {
            // Get the start and end points of the rectangle's longer side (e.g. east-west direction)
            let startPoint = start;
            let endPoint = [
                start[0] + (bounds.getNorthEast().lng - bounds.getSouthWest().lng) * Math.cos(initialHeading * Math.PI / 180),
                start[1] + (bounds.getNorthEast().lng - bounds.getSouthWest().lng) * Math.sin(initialHeading * Math.PI / 180)
            ];
            
            // Calculate the length of the rectangle's longer side
            const lineLength = bounds.getNorthEast().lng - bounds.getSouthWest().lng;

            // Initialize an array to hold the drawn lines
            const lines = [];
            const greyColor = 'grey';

            // Draw parallel lines
            while (startPoint[0] <= bounds.getNorthEast().lat) {
                // Draw a line from the current start point to the end point
                const line = L.polyline([startPoint, endPoint], {
                    color: greyColor,
                    weight: 2
                }).addTo(map);
                lines.push(line);

                // Calculate the next start and end points
                startPoint = [
                    startPoint[0] + trackSpacing,
                    startPoint[1]
                ];
                
                endPoint = [
                    endPoint[0] + trackSpacing,
                    endPoint[1]
                ];
            }

            return lines;
        }

        // Define the commence search point (CSP)
        const csp = [9.531347, 35.25444]; // CSP at the top-left corner of the rectangle

        // Set the initial heading, first turn, and track spacing
        const initialHeading = 90; // East direction (heading in degrees)
        const firstTurn = 'left'; // Direction of the first turn ('left' or 'right')
        const trackSpacing = 0.0003; // Spacing between each parallel line segment
        const distanceFromEdge = 0.0005; // Distance from the edge

        // Draw the creeping line ahead pattern inside the rectangle
        drawCLA(rectangle.getBounds(), csp, initialHeading, firstTurn, trackSpacing, distanceFromEdge);

        // Zoom the map to fit the bounds of the rectangle
        map.fitBounds(rectangle.getBounds());
    </script>
</body>
</html>

但是,我当前的实现并没有产生预期的 CLA 模式。任何有关如何使用 Leaflet.js 正确实现 CLA 模式的帮助将不胜感激。

javascript typescript leaflet react-leaflet
1个回答
0
投票

你提出问题的方式有些问题; 其一,您似乎使用经度和纬度作为

x
和f
y
坐标。一般来说,这是错误的,即使 该面积足够小,足以使地球的曲率 被忽略(如果它足够大而不能被忽略, 沿着大圆画的水平线 与并行显着不同)。但即使 我们沿着平行线、长度的水平线走 子午线
R * Δlatitude
的弧线,其中
R
是 地球的半径,而平行弧的长度 是
R * cos(latitude) * Δlongitude
。没有解决办法 这在问题数据中,因为参数如
trackSpacing
distanceFromEdge
给出 纬度/经度单位而不是距离(例如, 英里、公里)。不过,这个例子是在低纬度地区给出的, 其中
cos(latitude)
足够接近
1

我也不完全理解

distanceFromEdge
应该如何工作; 如果您在矩形的一个角处“输入”,则第一个 线总是在边缘,距离可以是 仅取自第二行。

现在,如果路径始终平行于

x
y
轴, 即,
initialHeading
0
90
180
270
(或
-90
)之一, 解决方案并不是很复杂,唯一的挑战是 计算当前行与边界的交点 矩形减去
distanceFromEdge
。我在 函数
computeLineEnd
,这是函数的中心点 代码;根据入口点,代码有 8 个测试用例 在边界矩形的四个角和沿着的初始线 在该角相交的两个矩形边中的一个或另一个。 可以从页面右上角选择这八个测试用例。 函数
computeCLAPolyline
返回 CLA 运行的折线; 然后将其绘制在地图上,入口点用绿色标记 圆圈和出口点为红色。

// Initialize the map and set its view to a given location and zoom level
const map = L.map('map').setView([9.5415, 35.2651], 14);

// Add an OpenStreetMap layer to the map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

const _bottom = 9.531347, // min latitude
    _top = 9.552678, // max latitude
    _left = 35.25444, // min longitude
    _right = 35.27577;  // max longitude

// Define the coordinates of the rectangle
const rectangleCoords = [
    [_bottom, _left], // bottom-left corner (latitude, longitude)
    [_top, _left], // top-left corner (latitude, longitude)
    [_top, _right], // top-right corner (latitude, longitude)
    [_bottom, _right]  // bottom-right corner (latitude, longitude)
];

// Create a polygon (rectangle) using the provided coordinates
const rectangle = L.polygon(rectangleCoords, {
    color: 'blue', // Color of the rectangle border
    weight: 0.5, // Thickness of the rectangle border
    fillColor: 'blue', // Fill color of the rectangle
    fillOpacity: 0.2 // Opacity of the fill color
}).addTo(map);

const MAX_LINES = 1000; // safety, to prevent infinite loop
let claPath = null, circleEntry = null, circleExit = null;
function drawCLA(csp, initialHeading, firstTurn, trackSpacing = 0.0004, distanceFromEdge = 0.0005){
    // Draw the creeping line ahead pattern inside the rectangle
    claPath?.remove();
    circleEntry?.remove();
    circleExit?.remove();

    const polyLine = computeCLAPolyline(rectangle.getBounds(), csp,
        initialHeading, firstTurn, trackSpacing, distanceFromEdge);
    if(polyLine.length === 0){
        return;
    }

    claPath = L.polyline(polyLine, {color: 'gray', weight: 2});
    claPath.addTo(map);
    // entry point
    circleEntry = L.circle(polyLine[0], 30, {color: 'green', fillColor: 'green', fillOpacity: 1});
    circleEntry.addTo(map);
    // exit point
    circleExit = L.circle(polyLine[polyLine.length-1], 30, {color: 'red', fillColor: 'red', fillOpacity: 1});
    circleExit.addTo(map);

}

document.querySelector('#tests').addEventListener('change',
    function(ev){
        const [sMarginV, sMarginH, sHeading, firstTurn] = ev.target.value.split(',');
        const marginH = sMarginH === 'left' ? _left : _right;
        const marginV = sMarginV === 'top' ? _top : _bottom;
        const initialHeading = parseFloat(sHeading);
        drawCLA([marginV, marginH], initialHeading, firstTurn);
    }
);

// Zoom the map to fit the bounds of the rectangle
map.fitBounds(rectangle.getBounds());

document.querySelector('#tests').dispatchEvent(new Event('change'));

////////////////////////////////////////////////////////////////////////////////////////////////////
function isInside({x, y}, boundsLines){
    return x >= boundsLines.constY1.x[0] && x <= boundsLines.constY1.x[1] &&
        y >= boundsLines.constX1.y[0] && y <= boundsLines.constX1.y[1];
}

function computeLineEnd({x0, y0}, {mSin, mCos,  maxLength = 1/0}, boundsLines, distanceFromEdge){
    //find the first intersection with the bounds of the line starting from (x0, y0) with slope mCos/mSin
    // the line equation: (y - y0) * mCos = (x-x0) * mSin
    const intersections = [];
    if(Math.abs(mSin) < 1e-10){
        mSin = 0;
    }
    if(Math.abs(mCos) < 1e-10){
        mCos = 0;
    }

    if(mCos !== 0){
        for(const boundLine of [boundsLines.constX1, boundsLines.constX2]){
            const xSol = boundLine.x + boundLine.marginSign * (distanceFromEdge || 0);
            if(mCos * (xSol - x0) > 0){
                const ySol = y0 + (xSol - x0) * mSin / mCos;
                if(ySol >= boundLine.y[0] && ySol <= boundLine.y[1]){
                    const delta2 = Math.sqrt((xSol - x0) ** 2 + (ySol - y0) ** 2);
                    if(delta2 > 1e-10 && isInside({x: xSol, y: ySol}, boundsLines)){
                        intersections.push({x: xSol, y: ySol, delta2});
                    }
                }
            }
        }
    }

    if(mSin !== 0){
        for(const boundLine of [boundsLines.constY1, boundsLines.constY2]){
            const ySol = boundLine.y + boundLine.marginSign * (distanceFromEdge || 0);
            if(mSin * (ySol - y0) > 0){
                const xSol = x0 + (ySol - y0) * mCos / mSin;
                if(xSol >= boundLine.x[0] && xSol <= boundLine.x[1]){
                    const delta2 = Math.sqrt((xSol - x0) ** 2 + (ySol - y0) ** 2);
                    if(delta2 > 1e-10 && isInside({x: xSol, y: ySol}, boundsLines)){
                        intersections.push({x: xSol, y: ySol, delta2})
                    }
                }
            }
        }
    }

    if(intersections.length > 1){
        intersections.sort(({delta2: a}, {delta2: b}) => b - a);
    }
    const firstIntersection =  intersections[0];
    if(firstIntersection && firstIntersection.delta2 > maxLength && distanceFromEdge !== false){
        return {x: x0 + maxLength * mCos, y: y0 + maxLength * mSin, delta2: maxLength};
    }
    return  firstIntersection;
}

function computeCLAPolyline(bounds, start, initialHeading, firstTurn, trackSpacing, distanceFromEdge) {
    const P1 = bounds.getNorthWest();
    const P2 = bounds.getNorthEast();
    const P3 = bounds.getSouthEast();
    const P4 = bounds.getSouthWest();

    const boundsLines = {
        constY1: {
            y: P1.lat,
            x: [P1.lng, P2.lng],
            marginSign: -1,
        },
        constY2: {
            y: P3.lat,
            x: [P4.lng, P3.lng],
            marginSign: 1
        },
        constX1: {
            x: P2.lng,
            y: [P3.lat, P2.lat],
            marginSign: -1
        },
        constX2: {
            x: P4.lng,
            y: [P4.lat, P1.lat],
            marginSign: 1
        }
    };

    let startPoint = start,
        startPointNoMargin = start;
    let lineAngle = (90-initialHeading) * Math.PI / 180;
    let maxLength = 1/0;
    let endOfLine = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
        {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, distanceFromEdge);

    if(!endOfLine){
        return [];
    }
    const resultPolyLine = [startPoint];

    let endOfLineNoMargin = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
        {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, false);

    let endPoint = [endOfLine.y, endOfLine.x];
    let endPointNoMargin = [endOfLineNoMargin.y, endOfLineNoMargin.x];

    let turn = firstTurn,
        turnIndex = 0;

    for(let i = 0; i < MAX_LINES; i++){
        lineAngle += turn === 'left' ? Math.PI / 2 : -Math.PI / 2;
        startPoint = endPoint;
        startPointNoMargin = endPointNoMargin;
        maxLength = maxLength === 1 / 0 ? trackSpacing : 1 / 0;
        endOfLine = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
            {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, distanceFromEdge);
        if(!endOfLine){
            resultPolyLine.push(startPointNoMargin);
            return resultPolyLine;
        }
        resultPolyLine.push(startPoint);
        endOfLineNoMargin = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
            {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, false);

        endPoint = [endOfLine.y, endOfLine.x];
        endPointNoMargin = [endOfLineNoMargin.y, endOfLineNoMargin.x];

        if(maxLength !== 1/0 && maxLength - endOfLine.delta2 > 1e-10){
            resultPolyLine.push(endPointNoMargin);
            return resultPolyLine;
        }
        turnIndex++;
        if(turnIndex % 2 === 0){
            turn = turn === 'left' ? 'right' : 'left';
        }
    }

    return [];
}
#map {
    height: 500px;
    width: 100%;
}
<link href="https://unpkg.com/leaflet/dist/leaflet.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>

<h2>Leaflet Creeping Line Ahead Pattern</h2>
<!-- Create a div element to hold the map -->
<div id="map"></div>
<select id="tests" style="position: absolute; top:1em; right:0">
    <option selected value="bottom,left,90,left">(bottom-left) -> right</option>
    <option value="bottom,left,0,right">(bottom-left) -> up</option>
    <option value="top,left,90,right">(top-left) -> right</option>
    <option value="top,left,180,left">(top-left) -> down</option>
    <option value="bottom,right,-90,right">(bottom-right) -> left</option>
    <option value="bottom,right,0,left">(bottom-right) -> up</option>
    <option value="top,right,-90,left">(top-right) -> left</option>
    <option value="top,right,180,right">(top-right) -> down</option>
</select>

或作为 jsFiddle

如果允许路径是倾斜的,计算量会更多 复杂,因为你必须在当前行之后向前看, 并在边缘之前结束,以便下一行也适合。这是 该想法的实现,与随机偏差 垂直/水平线:

// Initialize the map and set its view to a given location and zoom level
const map = L.map('map').setView([9.5415, 35.2651], 14);

// Add an OpenStreetMap layer to the map
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

const _bottom = 9.531347, // min latitude
    _top = 9.552678, // max latitude
    _left = 35.25444, // min longitude
    _right = 35.27577;  // max longitude

// Define the coordinates of the rectangle
const rectangleCoords = [
    [_bottom, _left], // bottom-left corner (latitude, longitude)
    [_top, _left], // top-left corner (latitude, longitude)
    [_top, _right], // top-right corner (latitude, longitude)
    [_bottom, _right]  // bottom-right corner (latitude, longitude)
];

// Create a polygon (rectangle) using the provided coordinates
const rectangle = L.polygon(rectangleCoords, {
    color: 'blue', // Color of the rectangle border
    weight: 0.5, // Thickness of the rectangle border
    fillColor: 'blue', // Fill color of the rectangle
    fillOpacity: 0.2 // Opacity of the fill color
}).addTo(map);


const MAX_LINES = 1000; // safety, to prevent infinite loop
let claPath = null, circleEntry = null, circleExit = null;
function drawCLA(csp, initialHeading, firstTurn, trackSpacing = 0.0004, distanceFromEdge = 0.0005){
    // Draw the creeping line ahead pattern inside the rectangle
    claPath?.remove();
    circleEntry?.remove();
    circleExit?.remove();
    console.log({csp, initialHeading, firstTurn}); // save problematic random values

    const polyLine = computeCLAPolyline(rectangle.getBounds(), csp,
        initialHeading, firstTurn, trackSpacing, distanceFromEdge);
    if(polyLine.length === 0){
        return;
    }

    claPath = L.polyline(polyLine, {color: 'gray', weight: 2});
    claPath.addTo(map);
    // entry point
    circleEntry = L.circle(polyLine[0], 30, {color: 'green', fillColor: 'green', fillOpacity: 1});
    circleEntry.addTo(map);
    // exit point
    circleExit = L.circle(polyLine[polyLine.length-1], 30, {color: 'red', fillColor: 'red', fillOpacity: 1});
    circleExit.addTo(map);
}

let maxObliqueness = null,
    marginH = null,
    marginV = null,
    mainHeading = null,
    changeSign = null,
    firstTurn = null;

function runRandomTest(){
    if([maxObliqueness, marginH, marginV, mainHeading, changeSign, firstTurn].includes(null)){
        return;
    }
    drawCLA([marginV, marginH], mainHeading+changeSign*Math.floor(1+Math.random()*maxObliqueness), firstTurn);
}

document.querySelector('#run').addEventListener('click', runRandomTest);

document.querySelector('#tests').addEventListener('change',
    function(ev){
        const [sMarginV, sMarginH, sHeading, sChangeSign, sFirstTurn] = ev.target.value.split(',');
        marginH = sMarginH === 'left' ? _left : _right;
        marginV = sMarginV === 'top' ? _top : _bottom;
        mainHeading = parseFloat(sHeading);
        changeSign = parseFloat(sChangeSign);
        firstTurn = sFirstTurn;

        runRandomTest();
    }
);

document.querySelector('#maxObliqueness').addEventListener('change',
    function(ev){
        maxObliqueness = parseFloat(ev.target.value);
        runRandomTest()
    }
);

// Zoom the map to fit the bounds of the rectangle
map.fitBounds(rectangle.getBounds());

// initialize data
document.querySelector('#tests').dispatchEvent(new Event('change'));
document.querySelector('#maxObliqueness').dispatchEvent(new Event('change'));

////////////////////////////////////////////////////////////////////////////////////////////////////

function isInside({x, y}, boundsLines){
    return x >= boundsLines.constY1.x[0] && x <= boundsLines.constY1.x[1] &&
        y >= boundsLines.constX1.y[0] && y <= boundsLines.constX1.y[1];
}

function computeLineEnd({x0, y0}, {mSin, mCos,  maxLength = 1/0}, boundsLines, distanceFromEdge){
    //find the first intersection with the bounds of the line starting from (x0, y0) with slope mCos/mSin
    // the line equation: (y - y0) * mCos = (x-x0) * mSin
    // or x = x0 + t * mCos; y = y0 + t * mSin with t >=0
    if(Math.abs(mSin) < 1e-10){
        mSin = 0;
    }
    if(Math.abs(mCos) < 1e-10){
        mCos = 0;
    }

    const intersections = [];
    if(mCos !== 0){
        for(const boundLine of [boundsLines.constX1, boundsLines.constX2]){
            const xSol = boundLine.x + boundLine.marginSign * (distanceFromEdge || 0);
            if(mCos * (xSol - x0) > 0){
                const ySol = y0 + (xSol - x0) * mSin / mCos;
                if(ySol >= boundLine.y[0] && ySol <= boundLine.y[1]){
                    const delta2 = Math.sqrt((xSol - x0) ** 2 + (ySol - y0) ** 2);
                    if(delta2 > 1e-10 && isInside({x: xSol, y: ySol}, boundsLines)){
                        intersections.push({x: xSol, y: ySol, delta2});
                    }
                }
            }
        }
    }

    if(mSin !== 0){
        for(const boundLine of [boundsLines.constY1, boundsLines.constY2]){
            const ySol = boundLine.y + boundLine.marginSign * (distanceFromEdge || 0);
            if(mSin * (ySol - y0) > 0){
                const xSol = x0 + (ySol - y0) * mCos / mSin;
                if(xSol >= boundLine.x[0] && xSol <= boundLine.x[1]){
                    const delta2 = Math.sqrt((xSol - x0) ** 2 + (ySol - y0) ** 2);
                    if(delta2 > 1e-10 && isInside({x: xSol, y: ySol}, boundsLines)){
                        intersections.push({x: xSol, y: ySol, delta2})
                    }
                }
            }
        }
    }

    if(intersections.length > 1){
        intersections.sort(({delta2: a}, {delta2: b}) => b - a);
    }
    const firstIntersection =  intersections[0];
    if(firstIntersection && firstIntersection.delta2 > maxLength && distanceFromEdge !== false){
        return {x: x0 + maxLength * mCos, y: y0 + maxLength * mSin, delta2: maxLength};
    }
    return  firstIntersection;
}

function computeLineEndWithParamStart({x0: [ax, bx], y0: [ay, by], maxT}, {mSin, mCos, maxLength}, boundsLines, distanceFromEdge){
    const tSols = [];
    for(const boundLine of [boundsLines.constX1, boundsLines.constX2]){
        const xSol = boundLine.x + boundLine.marginSign * distanceFromEdge;
        const t = (xSol - bx - maxLength * mCos) / ax;
        if(t >= 0 && t <= maxT){
            const ySol = ay * t + by + maxLength * mSin;
            if(isInside({x: xSol, y: ySol}, boundsLines)){
                tSols.push(t);
            }
        }
    }

    for(const boundLine of [boundsLines.constY1, boundsLines.constY2]){
        const ySol = boundLine.y + boundLine.marginSign * distanceFromEdge;
        const t = (ySol - by - maxLength * mSin) / ay;
        if(t >= 0 && t <= maxT){
            const xSol = ax * t + bx + maxLength * mCos;
            if(isInside({x: xSol, y: ySol}, boundsLines)){
                tSols.push(t);
            }
        }
    }

    if(tSols.length === 0){
        return null;
    }
    return Math.max(...tSols)
}


function computeNextTwoLines({x0, y0}, {mSin, mCos}, {mSin2, mCos2, maxLength2}, boundsLines, distanceFromEdge){
    const sol = computeLineEnd({x0, y0}, {mSin, mCos}, boundsLines, distanceFromEdge);
    if(!sol){
        return sol;
    }
    const maxT = sol.delta2;

    const t = computeLineEndWithParamStart(
        {x0: [mCos, x0], y0: [mSin, y0], maxT},
        {mSin: mSin2, mCos: mCos2, maxLength: maxLength2}, boundsLines, distanceFromEdge);

    if(t === null){
        return null;
    }
    else{
        return {
            x: x0 + t * mCos,
            y: y0 + t * mSin,
            x2: x0 + t * mCos + maxLength2 * mCos2,
            y2: y0 + t * mSin + maxLength2 * mSin2
        }
    }
}

// Function to draw the creeping line ahead pattern
function computeCLAPolyline(bounds, start, initialHeading, firstTurn, trackSpacing, distanceFromEdge) {
    const P1 = bounds.getNorthWest();
    const P2 = bounds.getNorthEast();
    const P3 = bounds.getSouthEast();
    const P4 = bounds.getSouthWest();

    const boundsLines = {
        constY1: {
            y: P1.lat,
            x: [P1.lng, P2.lng],
            marginSign: -1,
        },
        constY2: {
            y: P3.lat,
            x: [P4.lng, P3.lng],
            marginSign: 1
        },
        constX1: {
            x: P2.lng,
            y: [P3.lat, P2.lat],
            marginSign: -1
        },
        constX2: {
            x: P4.lng,
            y: [P4.lat, P1.lat],
            marginSign: 1
        }
    };

    // Get the start and end points of the rectangle's longer side (e.g. east-west direction)
    let startPoint = start,
        startPointNoMargin = start;

    let lineAngle = (90-initialHeading) * Math.PI / 180;
    let maxLength = 1/0;
    let endOfLine = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
        {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, distanceFromEdge);


    if(!endOfLine){
        return [];
    }
    const resultPolyLine = [startPoint];

    let endOfLineNoMargin = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
        {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, false);

    let endPoint = [endOfLine.y, endOfLine.x];
    let endPointNoMargin = [endOfLineNoMargin.y, endOfLineNoMargin.x];
    let prevPointNoMargin = null;

    let turn = firstTurn,
        turnIndex = 0;

    for(let i = 0; i < MAX_LINES; i++){
        let previousLineAngle = lineAngle;
        lineAngle += turn === 'left' ? Math.PI / 2 : -Math.PI / 2;
        startPoint = endPoint;
        startPointNoMargin = endPointNoMargin;
        maxLength = maxLength === 1 / 0 ? trackSpacing : 1 / 0;
        endOfLine = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
            {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, distanceFromEdge);
        if(endOfLine && (maxLength === 1/0 || maxLength - endOfLine.delta2 < 1e-10)){
            resultPolyLine.push(startPoint);
            endOfLineNoMargin = computeLineEnd({x0: startPoint[1], y0: startPoint[0]},
                {mSin: Math.sin(lineAngle), mCos: Math.cos(lineAngle), maxLength}, boundsLines, false);

            endPoint = [endOfLine.y, endOfLine.x];
            //L.circle(endPoint, maxLength !== 1/0 ? 20: 30, {color: maxLength !== 1/0 ? 'magenta' : 'orange', fillColor: maxLength !== 1/0 ? 'magenta' : 'orange', fillOpacity: 1}).addTo(map);
            endPointNoMargin = [endOfLineNoMargin.y, endOfLineNoMargin.x];
            prevPointNoMargin = null;
        }
        else{
            let sol2 = null;
            const mSin = Math.sin(previousLineAngle),
                mCos = Math.cos(previousLineAngle);
            const startPoint2 = resultPolyLine[resultPolyLine.length - 1];
            if(i % 2 === 0 && Math.abs(mSin) > 1e-5 && Math.abs(mCos) > 1e-5){
                sol2 = computeNextTwoLines({x0: startPoint2[1], y0: startPoint2[0]},
                    {mSin, mCos},
                    {mSin2: Math.sin(lineAngle), mCos2: Math.cos(lineAngle), maxLength2: trackSpacing},
                    boundsLines, distanceFromEdge);
            }
            if(sol2){
                startPoint = [sol2.y, sol2.x];
                endPoint = [sol2.y2, sol2.x2];

                const sol2NoMargin = computeNextTwoLines({x0: startPoint2[1], y0: startPoint2[0]},
                    {mSin, mCos},
                    {mSin2: Math.sin(lineAngle), mCos2: Math.cos(lineAngle), maxLength2: trackSpacing},
                    boundsLines, 0);
                if(sol2NoMargin){
                    prevPointNoMargin = [sol2NoMargin.y, sol2NoMargin.x];
                    endPointNoMargin = [sol2NoMargin.y2, sol2NoMargin.x2];
                }
                resultPolyLine.push(startPoint);
            }
            else{
                if(prevPointNoMargin){
                    resultPolyLine.push(prevPointNoMargin);
                }
                resultPolyLine.push(startPointNoMargin);
                return resultPolyLine;
            }
        }

        turnIndex++;
        if(turnIndex % 2 === 0){
            turn = turn === 'left' ? 'right' : 'left';
        }
    }

    return resultPolyLine;
}
#map {
    height: 500px;
    width: 100%;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>

<h2>Leaflet Creeping Line Ahead Pattern</h2>
<!-- Create a div element to hold the map -->
<div id="map"></div>
<div style="position: absolute; top:1em; right:0; text-align: right">
<select id="maxObliqueness">
    <option selected value="15">Random obliqueness: up to 15 deg</option>
    <option value="30">Random obliqueness: up to 30 deg</option>
    <option value="45">Random obliqueness: up to 45 deg</option>
</select><br>
<button id="run">Rerun</button>
<select id="tests">
    <option selected value="bottom,left,90,-1,left">(bottom-left) -> right</option>
    <option value="bottom,left,0,1,right">(bottom-left) -> up</option>
    <option value="top,left,90,1,right">(top-left) -> right</option>
    <option value="top,left,180,-1,left">(top-left) -> down</option>
    <option value="bottom,right,-90,1,right">(bottom-right) -> left</option>
    <option value="bottom,right,0,-1,left">(bottom-right) -> up</option>
    <option value="top,right,-90,-1,left">(top-right) -> left</option>
    <option value="top,right,180,1,right">(top-right) -> down</option>
</select>
</div>
或者作为 jsFiddle

这些应该被视为更准确的起点 版本,将考虑

cos(latitude)
术语, 甚至是地球的曲率,这可能要大得多 复杂。

© www.soinside.com 2019 - 2024. All rights reserved.