@canterbury-air-patrol/sar-search-patterns
Version:
Draw SAR Search Patterns
411 lines (407 loc) • 16.5 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var index_exports = {};
__export(index_exports, {
CreepingLineAheadSearch: () => CreepingLineAheadSearch,
ExpandingBoxSearch: () => ExpandingBoxSearch,
SearchConfiguration: () => SearchConfiguration,
SearchDisplay: () => SearchDisplay,
SearchLeg: () => SearchLeg,
SearchPattern: () => SearchPattern,
SectorSearch: () => SectorSearch
});
module.exports = __toCommonJS(index_exports);
// react.tsx
var import_react = __toESM(require("react"));
var import_react_bootstrap = require("react-bootstrap");
// sar-search-patterns.ts
var xy = class {
constructor(x, y) {
this.x = x;
this.y = y;
}
};
var SearchLeg = class {
constructor(from, to, distance, bearing) {
this.from = from;
this.to = to;
this.bearing = bearing;
this.distance = distance;
}
};
var SearchPattern = class {
constructor(sweepWidth) {
this.sweepWidth = sweepWidth;
this.searchLegs = [];
this.currentLeg = 1;
}
get leg() {
return this.searchLegs[this.currentLeg - 1];
}
get complete() {
return this.currentLeg > this.searchLegs.length;
}
get length() {
let length = 0;
for (const legIdx in this.searchLegs) {
const tmpLeg = this.searchLegs[legIdx];
length += tmpLeg.distance;
}
return length;
}
getLegs() {
return this.searchLegs;
}
nextLeg() {
this.currentLeg++;
}
uniqueKey() {
return `${this.searchType}-${this.sweepWidth}-${this.searchLegs}-${this.currentLeg}`;
}
};
function move(from, direction, distance) {
const to = { x: 0, y: 0 };
const rads = direction * Math.PI / 180;
to.x = from.x + Math.sin(rads) * distance;
to.y = from.y + Math.cos(rads) * distance;
return to;
}
var SectorSearch = class extends SearchPattern {
constructor(sweepWidth, multiplier, iterations, startingDirection) {
super(sweepWidth);
this.searchType = "sector";
this.startingDirection = Number(startingDirection);
if (this.startingDirection === void 0) {
this.startingDirection = 0;
}
this.multiplier = multiplier;
if (this.multiplier <= 0) {
this.multiplier = 1;
}
if (this.multiplier > 3) {
this.multiplier = 3;
}
this.iterations = iterations;
if (!(this.iterations === 1 || this.iterations === 2 || this.iterations === 3)) {
this.iterations = 1;
}
this.generateSearchLegs();
}
generateSearchLegs() {
this.searchLegs = [];
let currentBearing = this.startingDirection;
let lastPoint = { x: 0, y: 0 };
const legLength = this.sweepWidth * this.multiplier;
for (let i = 1; i < 9 * this.iterations + 1; i++) {
const from = lastPoint;
let to = { x: 0, y: 0 };
if (i % 3 !== 0) {
to = move(from, currentBearing, legLength);
}
this.searchLegs.push(new SearchLeg(from, to, legLength, currentBearing));
if (i % 3 === 0) {
if (i % 9 === 0) {
currentBearing = (currentBearing + 30) % 360;
}
} else {
currentBearing = (currentBearing + 120) % 360;
}
lastPoint = to;
}
}
uniqueKey() {
return `${this.searchType}-${this.sweepWidth}-${this.searchLegs}-${this.currentLeg}-${this.startingDirection}-${this.multiplier}-${this.iterations}`;
}
};
var ExpandingBoxSearch = class extends SearchPattern {
constructor(sweepWidth, iterations, startingDirection) {
super(sweepWidth);
this.searchType = "expandingbox";
this.iterations = iterations;
this.startingDirection = Number(startingDirection);
this.generateSearchLegs();
}
generateSearchLegs() {
this.searchLegs = [];
let direction = this.startingDirection;
let from = { x: 0, y: 0 };
for (let i = 0; i < this.iterations * 4; i++) {
const legLength = this.sweepWidth * (1 + Math.round((i - 1) / 2));
const to = move(from, direction, legLength);
this.searchLegs.push(new SearchLeg(from, to, this.sweepWidth * (1 + Math.round((i - 1) / 2)), direction));
direction = (direction + 90) % 360;
from = to;
}
}
uniqueKey() {
return `${this.searchType}-${this.sweepWidth}-${this.searchLegs}-${this.currentLeg}-${this.startingDirection}-${this.iterations}`;
}
};
var CreepingLineAheadSearch = class extends SearchPattern {
constructor(sweepWidth, legLength, legs, progressDirection) {
super(sweepWidth);
this.searchType = "creepingline";
this.legLength = Number(legLength);
this.legs = Number(legs);
this.progressDirection = Number(progressDirection);
this.generateSearchLegs();
}
generateSearchLegs() {
this.searchLegs = [];
const baseNear = new xy(0, 0);
const baseFar = move(baseNear, this.progressDirection + 90, this.legLength);
for (let i = 1; i < this.legs * 2; i++) {
let direction = this.progressDirection;
let from;
let to;
let distance;
const leg = Math.round(i / 2);
if (i % 4 === 0) {
from = move(baseNear, this.progressDirection, this.sweepWidth * leg);
to = move(baseNear, this.progressDirection, this.sweepWidth * (leg + 1));
distance = this.sweepWidth;
} else if (i % 4 === 1) {
from = move(baseNear, this.progressDirection, this.sweepWidth * leg);
to = move(baseFar, this.progressDirection, this.sweepWidth * leg);
direction = (direction + 90) % 360;
distance = this.legLength;
} else if (i % 4 === 2) {
from = move(baseFar, this.progressDirection, this.sweepWidth * leg);
to = move(baseFar, this.progressDirection, this.sweepWidth * (leg + 1));
distance = this.sweepWidth;
} else {
from = move(baseFar, this.progressDirection, this.sweepWidth * leg);
to = move(baseNear, this.progressDirection, this.sweepWidth * leg);
direction = (direction + 270) % 360;
distance = this.legLength;
}
this.searchLegs.push(new SearchLeg(from, to, distance, direction));
}
}
uniqueKey() {
return `${this.searchType}-${this.sweepWidth}-${this.searchLegs}-${this.currentLeg}-${this.legLength}-${this.legs}-${this.progressDirection}`;
}
};
// react.tsx
var SearchDisplay = class extends import_react.default.Component {
constructor(props) {
super(props);
this.state = {
search: this.props.search
};
this.canvasRef = import_react.default.createRef();
this.minX = 0;
this.minY = 0;
this.maxX = 0;
this.maxY = 0;
}
updateX(x) {
if (x < this.minX) {
this.minX = x;
}
if (x > this.maxX) {
this.maxX = x;
}
}
updateY(y) {
if (y < this.minY) {
this.minY = y;
}
if (y > this.maxY) {
this.maxY = y;
}
}
determineXYRange(searchLegs) {
this.minX = 0;
this.minY = 0;
this.maxX = 0;
this.maxY = 0;
for (const idx in searchLegs) {
const leg = searchLegs[idx];
this.updateX(leg.from.x);
this.updateY(leg.from.y);
this.updateX(leg.to.x);
this.updateY(leg.to.y);
}
return [Math.abs(this.maxX - this.minX), Math.abs(this.maxY - this.minY)];
}
drawSearch(canvas) {
const ctx = canvas.getContext("2d");
if (ctx === null) {
return;
}
const searchLegs = this.state.search.getLegs();
const range = this.determineXYRange(searchLegs);
const offsetX = Math.abs(this.minX) + 10;
const offsetY = Math.abs(this.maxY) + 10;
range[0] += 20;
range[1] += 20;
const scaleX = canvas.width / range[0];
const scaleY = canvas.height / range[1];
let scale = scaleX;
if (scaleY < scaleX) {
scale = scaleY;
}
ctx.scale(scale, scale);
for (const idx in searchLegs) {
const leg = searchLegs[idx];
ctx.beginPath();
ctx.lineWidth = 5;
if (this.state.search.leg === leg) {
ctx.strokeStyle = "orange";
} else {
ctx.strokeStyle = "grey";
}
ctx.moveTo(offsetX + leg.from.x, offsetY - leg.from.y);
ctx.lineTo(offsetX + leg.to.x, offsetY - leg.to.y);
ctx.stroke();
}
}
componentDidMount() {
const canvas = this.canvasRef.current;
if (canvas !== null) {
this.drawSearch(canvas);
}
}
render() {
return /* @__PURE__ */ import_react.default.createElement("canvas", { ref: this.canvasRef, width: 600, height: 600 });
}
};
function constructSearch(values) {
if (values.searchType === "sector") {
return new SectorSearch(values.sweepWidth, values.multiplier, values.iterations, values.initialDirection);
}
if (values.searchType === "expandingbox") {
return new ExpandingBoxSearch(values.sweepWidth, values.iterations, values.initialDirection);
}
if (values.searchType === "creepingline") {
return new CreepingLineAheadSearch(values.sweepWidth, values.legLength, values.iterations, values.initialDirection);
}
}
var SearchConfiguration = class extends import_react.default.Component {
constructor(props) {
super(props);
this.handleStateUpdate = this.handleStateUpdate.bind(this);
this.handleChangeSearch = this.handleChangeSearch.bind(this);
this.handleChangeSweepWidth = this.handleChangeSweepWidth.bind(this);
this.handleChangeMultiplier = this.handleChangeMultiplier.bind(this);
this.handleChangeLegLength = this.handleChangeLegLength.bind(this);
this.handleChangeIterations = this.handleChangeIterations.bind(this);
this.handleChangeDirection = this.handleChangeDirection.bind(this);
this.state = {
searchType: "sector",
sweepWidth: 200,
legLength: 1e3,
iterations: 1,
initialDirection: 0,
multiplier: 1
};
}
handleStateUpdate() {
if (this.props.updateSearch !== void 0) {
this.props.updateSearch(constructSearch(this.state));
}
}
handleChangeSearch(event) {
const target = event.target;
const value = target.value;
this.setState({ searchType: value }, this.handleStateUpdate);
}
handleChangeSweepWidth(event) {
const target = event.target;
const value = Number(target.value);
this.setState({ sweepWidth: value }, this.handleStateUpdate);
}
handleChangeLegLength(event) {
const target = event.target;
const value = Number(target.value);
this.setState({ legLength: value }, this.handleStateUpdate);
}
handleChangeMultiplier(event) {
const target = event.target;
const value = Number(target.value);
this.setState({ multiplier: value }, this.handleStateUpdate);
}
handleChangeIterations(event) {
const target = event.target;
const value = Number(target.value);
this.setState({ iterations: value }, this.handleStateUpdate);
}
handleChangeDirection(event) {
const target = event.target;
const value = Number(target.value);
this.setState({ initialDirection: value }, this.handleStateUpdate);
}
render() {
const labels = [];
const inputs = [];
if (this.state.searchType === "sector" || this.state.searchType === "expandingbox") {
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "iterations" }, "Iterations"));
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "iterations" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeIterations, value: this.state.iterations }))
);
if (this.state.searchType === "sector") {
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "multiplier" }, "Multiplier"));
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "multiplier" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeMultiplier, value: this.state.multiplier }))
);
}
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "direction" }, "Initial Direction"));
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "direction" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeDirection, value: this.state.initialDirection }))
);
} else if (this.state.searchType === "creepingline") {
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "legs" }, "Leg Length"));
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "iterations" }, "Legs"));
labels.push(/* @__PURE__ */ import_react.default.createElement("td", { key: "direction" }, "Progress Direction"));
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "legs" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeLegLength, value: this.state.legLength }))
);
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "iterations" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeIterations, value: this.state.iterations }))
);
inputs.push(
/* @__PURE__ */ import_react.default.createElement("td", { key: "direction" }, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeDirection, value: this.state.initialDirection }))
);
}
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Table, null, /* @__PURE__ */ import_react.default.createElement("thead", null, /* @__PURE__ */ import_react.default.createElement("tr", null, /* @__PURE__ */ import_react.default.createElement("td", null, "Search Type"), /* @__PURE__ */ import_react.default.createElement("td", null, "Sweep Width"), labels)), /* @__PURE__ */ import_react.default.createElement("tbody", null, /* @__PURE__ */ import_react.default.createElement("tr", null, /* @__PURE__ */ import_react.default.createElement("td", null, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Select, { onChange: this.handleChangeSearch }, /* @__PURE__ */ import_react.default.createElement("option", { value: "sector" }, "Sector"), /* @__PURE__ */ import_react.default.createElement("option", { value: "expandingbox" }, "Expanding Box"), /* @__PURE__ */ import_react.default.createElement("option", { value: "creepingline" }, "Creeping Line"))), /* @__PURE__ */ import_react.default.createElement("td", null, /* @__PURE__ */ import_react.default.createElement(import_react_bootstrap.Form.Control, { type: "number", onChange: this.handleChangeSweepWidth, value: this.state.sweepWidth })), inputs))));
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CreepingLineAheadSearch,
ExpandingBoxSearch,
SearchConfiguration,
SearchDisplay,
SearchLeg,
SearchPattern,
SectorSearch
});
//# sourceMappingURL=index.js.map