У меня есть требование нарисовать шаблон «ползущая линия вперед» (CLA) с помощью 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. Будем очень признательны за любую помощь в том, как правильно реализовать шаблон CLA с использованием Leaflet.js.
Есть некоторые проблемы с тем, как вы ставите проблему;
во-первых, вы, кажется, используете долготу и широту как
x
и 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.info({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)
,
или даже кривизна Земли, которая, вероятно, гораздо больше
сложный.