我需要使用 Leaflet.js 在任务区域(正方形、矩形和通用多边形)上绘制蠕动线(CLA)图案。 CLA 图案包括平行线,其直线段比要搜索区域的总宽度短。
搜索模式选项包括:
这是我写的相关代码:
<!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: '© <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 模式的帮助将不胜感激。
你提出问题的方式有些问题; 其一,您似乎使用经度和纬度作为
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: '© <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: '© <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>
这些应该被视为更准确的起点 版本,将考虑
cos(latitude)
术语,
甚至是地球的曲率,这可能要大得多
复杂。