UNPKG

@canterbury-air-patrol/marine-search-area-coverage

Version:
370 lines 17.2 kB
// marine-search-area-coverage.tsx import { marine_sweep_widths as marineSweepWidths, marine_sweep_width_weather_corrections as weatherCorrections } from "@canterbury-air-patrol/marine-sweep-width-data"; import React from "react"; import Table from "react-bootstrap/Table"; var MarineSAC = class { constructor(column, assetType) { this.column = column; this.asset_type = assetType; this.asset_speed = 0; this.wu = 0; this.speed_correction = 1; this.fw = 0; this.sweep_width = 0; this.fatigue = false; this.corrected_sweep_width = 0; this.practical_track_spacing = 0; this.search_area = 144; this.search_hours = 0; this.available_search_hours = 0; this.modified_area = 0; this.whole_area_practical_track_spacing = 0; } recaclculate() { this.sweep_width = this.wu * this.speed_correction * this.fw; if (this.fatigue) { this.corrected_sweep_width = this.sweep_width * 0.9; } else { this.corrected_sweep_width = this.sweep_width; } this.search_hours = this.search_area / (this.asset_speed * this.practical_track_spacing); this.modified_area = this.practical_track_spacing * this.asset_speed * this.available_search_hours; this.whole_area_practical_track_spacing = this.search_area / (this.available_search_hours * this.asset_speed); } }; var tableRows = [ { display_name: "Uncorrected Sweep Width (Wu) `NM`", column_name: "wu" }, { display_name: "Speed Correction Factor (Sc)", column_name: "speed_correction" }, { display_name: "Weather Corrected Factor (Fw)", column_name: "fw" }, { display_name: "Sweep Width (W) (Wu x Sc x Fw)", column_name: "sweep_width" }, { display_name: "Fatigue Factor (Ff)", column_name: "fatigue" }, { display_name: "Corrected Sweep Width (W X Ff)", column_name: "corrected_sweep_width" }, { display_name: "Practical Track Spacing `NM`", column_name: "practicalTrackSpacing", input: true, input_type: "number" }, { display_name: "Search Area", column_name: "search_area", input: true, input_type: "number" }, { display_name: "Search Hours (T) Total", column_name: "search_hours" }, { display_name: "Available Search Hours", column_name: "availableSearchHours", input: true, input_type: "number" }, { display_name: "Modified Area at Practical Spacing in Available Hours", column_name: "modified_area" }, { display_name: "Track Spacing for Whole Area in Available Time", column_name: "whole_area_practical_track_spacing" } ]; var WeatherDataTable = class extends React.Component { constructor(props) { super(props); this.windSpeed = 0; this.seaHeight = 0; this.updateWeatherImpact(); this.handleChange = this.handleChange.bind(this); } updateWeatherImpact() { let weatherImpact = "low"; if (this.windSpeed >= 25 || this.seaHeight >= 1.5) { weatherImpact = "high"; } else if (this.windSpeed >= 15 || this.seaHeight >= 1) { weatherImpact = "medium"; } this.props.weatherImpactChange(weatherImpact); } handleChange(event) { const target = event.target; const value = target.value; const name = target.name; if (name === "met_visibility") { this.props.weatherVisibilityChange(parseFloat(value)); } else { if (name === "wind_speed") { this.windSpeed = parseFloat(value); } else if (name === "sea_height") { this.seaHeight = parseFloat(value); } this.updateWeatherImpact(); } } render() { return /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("label", { htmlFor: "met_visibility" }, "Meteorological Visibility (NM)")), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("input", { type: "number", id: "met_visibility", name: "met_visibility", onChange: this.handleChange, defaultValue: this.props.metVisibility }))), /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("label", { htmlFor: "wind_speed" }, "Wind Speed (knots)")), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("input", { type: "number", id: "wind_speed", name: "wind_speed", onChange: this.handleChange, defaultValue: this.windSpeed }))), /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("label", { htmlFor: "sea_height" }, "Sea Height (meters)")), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("input", { type: "number", id: "sea_height", name: "sea_height", onChange: this.handleChange, defaultValue: this.seaHeight })))); } }; var TargetTypeSelector = class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(event) { const target = event.target; const value = target.value; this.props.targetTypeChange(value); } render() { const selectObjects = []; for (const idx in this.props.possible_targets) { const target = this.props.possible_targets[idx]; selectObjects.push( /* @__PURE__ */ React.createElement("option", { key: target, value: target }, target) ); } return /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("select", { defaultValue: this.props.selected, onChange: this.handleChange }, selectObjects)))); } }; var AssetSpeed = class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(event) { const target = event.target; const value = target.value; this.props.assetSpeedChange(this.props.assetType, parseFloat(value)); } render() { return /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("label", { htmlFor: this.props.assetType + "_speed" }, this.props.assetType, " search speed (knots)")), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("input", { type: "number", name: "asset_speed", onChange: this.handleChange })))); } }; var Fatigue = class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(event) { const target = event.target; const value = target.checked; this.props.fatigueChanged(value); } render() { return /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("label", { htmlFor: "fatigue" }, "Fatigue")), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("input", { type: "checkbox", name: "fatigue", id: "fatigue", defaultChecked: this.props.fatigue, onChange: this.handleChange })))); } }; var DataTable = class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(event) { const target = event.target; const value = target.value; const name = target.id; const idParts = name.split("_"); const assetID = idParts.slice(0, 2).join("_"); const columnName = idParts.slice(2).join("_"); this.props.updateData(assetID, columnName, parseFloat(value)); } render() { const rows = []; const assetTypes = [/* @__PURE__ */ React.createElement("th", { key: "head" }, "Asset Type:")]; const assetHeights = [/* @__PURE__ */ React.createElement("th", { key: "head" }, "Height of Eye:")]; for (const idx in tableRows) { const htmlColumns = []; htmlColumns.push(/* @__PURE__ */ React.createElement("th", { key: "head" }, tableRows[idx].display_name)); for (const colIdx in this.props.columns) { const column = this.props.columns[colIdx]; if (tableRows[idx].input) { htmlColumns.push( /* @__PURE__ */ React.createElement("td", { key: idx + "_" + colIdx }, /* @__PURE__ */ React.createElement( "input", { type: tableRows[idx].input_type, id: column.asset_type + "_" + column.column + "_" + tableRows[idx].column_name, defaultValue: column[tableRows[idx].column_name], onChange: this.handleChange } )) ); } else if (tableRows[idx].column_name === "fatigue") { htmlColumns.push(/* @__PURE__ */ React.createElement("td", { key: idx + "_" + colIdx }, column[tableRows[idx].column_name] ? 0.9 : 1)); } else { htmlColumns.push(/* @__PURE__ */ React.createElement("td", { key: idx + "_" + colIdx }, column[tableRows[idx].column_name])); } } rows.push(/* @__PURE__ */ React.createElement("tr", { key: idx }, htmlColumns)); } for (const colIdx in this.props.columns) { const column = this.props.columns[colIdx]; assetTypes.push(/* @__PURE__ */ React.createElement("th", { key: colIdx }, column.asset_type)); assetHeights.push(/* @__PURE__ */ React.createElement("th", { key: colIdx }, column.column)); } return /* @__PURE__ */ React.createElement("form", null, /* @__PURE__ */ React.createElement(Table, null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, assetTypes), /* @__PURE__ */ React.createElement("tr", null, assetHeights)), /* @__PURE__ */ React.createElement("tbody", null, rows))); } }; var MarineSACTable = class extends React.Component { constructor(props) { super(props); this.possibleTargetsList = Object.keys(marineSweepWidths); this.state = { columns: [], targetType: this.possibleTargetsList[0], fatigue: false, weatherImpact: "low", assetSpeeds: { Boat: 0, Aircraft: 0 }, metVisibility: 10, practicalTrackSpacing: /* @__PURE__ */ new Map(), availableSearchHours: /* @__PURE__ */ new Map() }; this.weatherImpactChange = this.weatherImpactChange.bind(this); this.weatherVisibilityChange = this.weatherVisibilityChange.bind(this); this.targetTypeChange = this.targetTypeChange.bind(this); this.assetSpeedChange = this.assetSpeedChange.bind(this); this.fatigueChange = this.fatigueChange.bind(this); this.updateData = this.updateData.bind(this); const defaultAssets = [ { asset_type: "Boat", search_height: "8ft" }, { asset_type: "Boat", search_height: "14ft" }, { asset_type: "Aircraft", search_height: "500ft" }, { asset_type: "Aircraft", search_height: "1000ft" } ]; for (const assetIdx in defaultAssets) { const asset = defaultAssets[assetIdx]; this.state.columns.push(new MarineSAC(asset.search_height, asset.asset_type)); this.state.practicalTrackSpacing.set(`${asset.asset_type}_${asset.search_height}`, 0); this.state.availableSearchHours.set(`${asset.asset_type}_${asset.search_height}`, 0); } } weatherImpactChange(newWeatherImpact) { this.setState({ weatherImpact: newWeatherImpact }); } weatherVisibilityChange(metVisibility) { this.setState({ metVisibility }); } targetTypeChange(newTargetType) { this.setState({ targetType: newTargetType }); } assetSpeedChange(assetType, speed) { this.setState(function(oldState) { const currentAssetSpeeds = oldState.assetSpeeds; currentAssetSpeeds[assetType] = speed; return { assetSpeeds: currentAssetSpeeds }; }); } fatigueChange(value) { this.setState({ fatigue: value }); } updateData(assetID, fieldName, value) { if (fieldName === "practicalTrackSpacing") { this.setState(function(oldState) { const currentData = oldState.practicalTrackSpacing; currentData.set(assetID, value); return { practicalTrackSpacing: currentData }; }); } if (fieldName === "availableSearchHours") { this.setState(function(oldState) { const currentData = oldState.availableSearchHours; currentData.set(assetID, value); return { availableSearchHours: currentData }; }); } } recalculate() { const targetData = marineSweepWidths[this.state.targetType]; for (const colIdx in this.state.columns) { const column = this.state.columns[colIdx]; const searchHeight = column.column; const assetType = column.asset_type; const columnName = `${assetType}_${searchHeight}`; const visibleDistanceData = assetType == "Boat" ? targetData.Boat[searchHeight] : assetType === "Heliocopter" ? targetData.Helicopter[searchHeight] : assetType === "Aircraft" ? targetData.Aircraft[searchHeight] : null; const speedCorrections = assetType === "Aircraft" ? targetData.speed_corrections.Aircraft : assetType === "Helicopter" ? targetData.speed_corrections.Helicopter : null; column.asset_speed = Number(this.state.assetSpeeds[column.asset_type]); let lowerSpeedSeen = null; let higherSpeedSeen = null; if (speedCorrections === null) { column.speed_correction = 1; } else { for (const idx in speedCorrections) { const data = speedCorrections[idx]; if ((lowerSpeedSeen === null || data.speed > lowerSpeedSeen.speed) && data.speed <= column.asset_speed) { lowerSpeedSeen = data; } if ((higherSpeedSeen === null || data.speed < higherSpeedSeen.speed) && data.speed >= column.asset_speed) { higherSpeedSeen = data; } } if (lowerSpeedSeen === null && higherSpeedSeen !== null) { column.speed_correction = higherSpeedSeen.correction; } else if (lowerSpeedSeen !== null && higherSpeedSeen === null) { column.speed_correction = lowerSpeedSeen.correction; } else if (lowerSpeedSeen !== null && higherSpeedSeen !== null) { if (lowerSpeedSeen.correction === higherSpeedSeen.correction) { column.speed_correction = lowerSpeedSeen.correction; } else { const speedRange = higherSpeedSeen.speed - lowerSpeedSeen.speed; const correctionRange = higherSpeedSeen.correction - lowerSpeedSeen.correction; const assetSpeedOffset = column.asset_speed - lowerSpeedSeen.speed; const ratio = assetSpeedOffset / speedRange; column.speed_correction = lowerSpeedSeen.correction + ratio * correctionRange; } } else { column.speed_correction = 1; } } let highestSeenSweepWidth = 0; let highestSeenVis = 0; if (visibleDistanceData) { for (const idx in visibleDistanceData) { const data = visibleDistanceData[idx]; if (data.vis <= this.state.metVisibility && data.vis > highestSeenVis) { highestSeenSweepWidth = data.sw; highestSeenVis = data.vis; } } } column.wu = highestSeenSweepWidth; const weatherCorrectionsTable = targetData.weather_corrections === "large" ? weatherCorrections.large : weatherCorrections.small; if (this.state.weatherImpact === "low" || this.state.weatherImpact === "medium" || this.state.weatherImpact === "high") { column.fw = weatherCorrectionsTable.IAMSAR[this.state.weatherImpact]; } column.fatigue = this.state.fatigue; column.practical_track_spacing = this.state.practicalTrackSpacing.get(columnName); column.available_search_hours = this.state.availableSearchHours.get(columnName); column.recaclculate(); } } render() { this.recalculate(); return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Table, null, /* @__PURE__ */ React.createElement(WeatherDataTable, { weatherImpactChange: this.weatherImpactChange, weatherVisibilityChange: this.weatherVisibilityChange, metVisibility: this.state.metVisibility }), /* @__PURE__ */ React.createElement(AssetSpeed, { assetType: "Boat", assetSpeedChange: this.assetSpeedChange }), /* @__PURE__ */ React.createElement(AssetSpeed, { assetType: "Aircraft", assetSpeedChange: this.assetSpeedChange }), /* @__PURE__ */ React.createElement(TargetTypeSelector, { possible_targets: this.possibleTargetsList, targetTypeChange: this.targetTypeChange, selected: this.state.targetType }), /* @__PURE__ */ React.createElement(Fatigue, { fatigueChanged: this.fatigueChange, fatigue: this.state.fatigue })), /* @__PURE__ */ React.createElement(DataTable, { columns: this.state.columns, updateData: this.updateData })); } }; export { MarineSACTable }; //# sourceMappingURL=marine-search-area-coverage.mjs.map