From 4bb4e53623ba3c3a452cfa02eb6c6429c7a37d2a Mon Sep 17 00:00:00 2001 From: william blakc Date: Sat, 3 Dec 2022 12:39:35 -0500 Subject: [PATCH] Mission Planning --- client/public/assets/icon-loiter.png | Bin 0 -> 11248 bytes client/public/assets/icon-time.png | Bin 0 -> 11248 bytes client/public/assets/icon-turn.png | Bin 0 -> 12167 bytes client/public/assets/icon-unlim.png | Bin 0 -> 12825 bytes client/src/commands.js | 10 + client/src/components/FlightMap.js | 254 ++++++++++++++---- client/src/components/UIElements/Button.js | 22 +- client/src/components/UIElements/Switch.js | 9 + client/src/pages/FlightData/index.js | 20 +- .../tabs/FlightPlan/FlightPlanToolbar.js | 99 ++++--- 10 files changed, 304 insertions(+), 110 deletions(-) create mode 100644 client/public/assets/icon-loiter.png create mode 100644 client/public/assets/icon-time.png create mode 100644 client/public/assets/icon-turn.png create mode 100644 client/public/assets/icon-unlim.png create mode 100644 client/src/commands.js create mode 100644 client/src/components/UIElements/Switch.js diff --git a/client/public/assets/icon-loiter.png b/client/public/assets/icon-loiter.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbd95a0a3e88c29392f0f2b1a61b7f2e038f3ab GIT binary patch literal 11248 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 yg)!HCcOzr2SJ)h?;TWn5u}PB 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 yg)!HW>gr|@R?IP~i6#;18eVa{mhkGRN- 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 literal 0 HcmV?d00001 diff --git a/client/public/assets/icon-unlim.png b/client/public/assets/icon-unlim.png new file mode 100644 index 0000000000000000000000000000000000000000..7d09bcc5d64f5d3ae29602d7133a403c112484a9 GIT binary patch literal 12825 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 OfRdcLY?ZWm(EkBTid9em literal 0 HcmV?d00001 diff --git a/client/src/commands.js b/client/src/commands.js new file mode 100644 index 00000000..a15c5c5e --- /dev/null +++ b/client/src/commands.js @@ -0,0 +1,10 @@ + +const Commands = { + waypoint: 16, + unlimLoiter: 17, + turnLoiter: 18, + timeLoiter: 19, + jump: 177 +} + +export default Commands \ No newline at end of file diff --git a/client/src/components/FlightMap.js b/client/src/components/FlightMap.js index c7739b4d..139c094c 100644 --- a/client/src/components/FlightMap.js +++ b/client/src/components/FlightMap.js @@ -5,11 +5,41 @@ import { MapContainer, TileLayer, Tooltip, Marker, Polyline, Circle, LayersContr import { httpget } from "../backend.js" import L from "leaflet" +import Commands from "../commands.js" import PolylineDecorator from "../pages/FlightData/tabs/FlightPlan/PolylineDecorator.js" import RotatedMarker from "./RotatedMarker.js" import { useInterval } from "../util" import { Box, Button } from "components/UIElements" import { red } from "theme/Colors" +import { first, get } from "lodash" +import { promiseImpl } from "ejs" +import { hasSelectionSupport } from "@testing-library/user-event/dist/utils/index.js" + +// v is onChange value, value is validated value (i.e. your data because to have been set it must have been validated), set is setter +const signedFloatValidation = (v, value, set) => { + if (!Number.isNaN(Number(v)) && v.length > 0) { + if (v.endsWith(".")) { + set(null) + } else { + set(Number(v)) + } + return v + } else if (v.substring(0, v.length - 1).endsWith(".")) { + return v.substring(0, v.length - 1) + } else if (v.length === 0) { + set(null) + return v + } else if (v.substring(0, Math.max(v.length - 1, 1)) === "-") { + set(null) + return v.substring(0, Math.max(v.length - 1, 1)) + } else if (Number.isNaN(parseFloat(v))) { + return "" + } + + return value +} + +const EMPTY_JUMP = -1 const FlightPlanMap = props => { const [state, setState] = useState({ @@ -20,6 +50,8 @@ const FlightPlanMap = props => { const [icons, setIcons] = useState({}) const tileRef = useRef(null) + const [firstJump, setFirstJump] = useState(EMPTY_JUMP) + useEffect(() => { httpget("/interop/mission", response => { setState(response.data.result.mapCenterPos) @@ -51,7 +83,7 @@ const FlightPlanMap = props => { return { num: marker.num, cmd: marker.cmd, p1: marker.p1, p2: marker.p2, lat: marker.lat, lng: marker.lon, alt: marker.alt * 3.281 } // convert altitude from meters to feet }) props.setters.path(points) - props.setters.pathSave(points) + props.setters.pathSave(structuredClone(points)) }) var MarkerIcon = L.Icon.extend({ @@ -90,6 +122,10 @@ const FlightPlanMap = props => { searchGrid: new MarkerIcon({ iconUrl: "../assets/icon-searchGrid.png" }), path: new MarkerIcon({ iconUrl: "../assets/icon-path.png" }), home: new MarkerIcon({ iconUrl: "../assets/icon-home.png" }), + unlim: new MarkerIcon({ iconUrl: "../assets/icon-unlim.png" }), + time: new MarkerIcon({ iconUrl: "../assets/icon-time.png" }), + turn: new MarkerIcon({ iconUrl: "../assets/icon-turn.png" }), + jump: new MarkerIcon({ iconUrl: "../assets/icon-waypoints.png" }), uav: new VehicleIcon({ iconUrl: "../assets/uav.svg" }), uavDirection: new DirectionPointerIcon({ iconUrl: "../assets/pointer.svg" }), uavDirectionOutline: new DirectionPointerIcon({ iconUrl: "../assets/pointer-outline.svg" }), @@ -106,6 +142,10 @@ const FlightPlanMap = props => { }, []) + useEffect(() => { + setFirstJump(EMPTY_JUMP) + }, [props.mode, props.placementMode]) + const checkInternet = () => { if (navigator.onLine) { fetch("https://g.co", { @@ -138,19 +178,56 @@ const FlightPlanMap = props => { let get = props.getters["path"] let set = props.setters["path"] let temp = get.slice() + if (datatype == "unlim" || datatype == "time" || datatype == "turn") { + datatype = "path" + } let loc = { ...props.getters[datatype][idx], lat: event.target.getLatLng().lat, lng: event.target.getLatLng().lng, opacity: 0.5 } temp[idx] = loc set(temp) props.setSaved(false) } + const jumpClick = (key, datatype) => { + if (props.placementMode === "disabled" || props.mode !== "jump") { + return + } + if (datatype === "unlim" || datatype === "turn" || datatype === "time" || datatype === "path") { + if (firstJump === -1) { + setFirstJump(key) + } else { + let path = props.getters.path.slice() + let point = { num: firstJump + 1, cmd: Commands.jump, p1: key + (key < firstJump ? 0 : 1), p2: 3 } + props.setSaved(false) + props.setters.path([...path.slice(0, firstJump).map(p => { + if (p.cmd == Commands.jump) { + if (p.p1 > firstJump) { + p.p1 += 1 + } + } + + return p + }), point, ...path.slice(firstJump, path.length).map(p => { + p.num += 1 + if (p.p1 > firstJump) { + p.p1 += 1 + } + return p + })]) + setFirstJump(EMPTY_JUMP) + } + } + } + const popup = (latlng, key, datatype, popupMenu, draggable) => { return ( { handleMove(event, key - (props.getters.path[0].num === 0 ? 0 : 1), datatype) } + dragend: (event) => { handleMove(event, key - (props.getters.path[0].num === 0 ? 0 : 1), datatype) }, + click: () => { + jumpClick(key, datatype) + } }} onkeydown={event => handleKeyPress(event, key)} draggable={draggable} @@ -179,23 +256,23 @@ const FlightPlanMap = props => { } const handleClick = event => { + if (props.placementMode === "disabled" || props.mode === "jump") { + return + } if (props.mode) { let get = props.getters["path"] let set = props.setters["path"] - if (props.mode === "push" || (props.mode === "insert" && get.length < 2)) { + + if (props.placementMode === "push" || (props.placementMode === "insert" && get.length < 2)) { let temp = get.slice() - let point = { lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5, num: get.length + (get[0]?.num === 0 ? -1 : 1) } - if (temp[temp.length - 1]?.cmd === 177) { - temp = [...temp.slice(0, temp.length - 1), point, temp[temp.length - 1]] - } else { - temp.push(point) - } + let point = { lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5, num: get.length + (get[0]?.num === 0 ? -1 : 1), cmd: Commands[props.mode] } + temp.push(point) set(temp) - } else if (props.mode === "insert") { + } else if (props.placementMode === "insert") { const getPerpendicularDistance = (i) => { let first let second - if (get[i]?.cmd === 177) { + if (get[i]?.cmd === Commands.jump) { first = get[i - 1] second = get[get[i].p1 - (get[0].num === 0 ? 0 : 1)] } else { @@ -240,16 +317,13 @@ const FlightPlanMap = props => { } let path = get.slice() - if (get[min]?.cmd === 177) { - path = [...path.slice(0, min), { num: min, lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5 }, ...(path.slice(min).map(point => ({ ...point, num: point.num + 1 })))] + if (get[min]?.cmd === Commands.jump) { + path = [...path.slice(0, min), { num: min, lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5, cmd: Commands[props.mode] }, ...(path.slice(min).map(point => ({ ...point, num: point.num + 1 })))] } else { - path = [...path.slice(0, min + 1), { num: min + (get[0]?.num === 0 ? 1 : 2), lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5 }, ...(path.slice(min + 1).map(point => ({ ...point, num: point.num + 1 })))] + path = [...path.slice(0, min + 1), { num: min + (get[0]?.num === 0 ? 1 : 2), lat: event.latlng.lat, lng: event.latlng.lng, opacity: 0.5, cmd: Commands[props.mode] }, ...(path.slice(min + 1).map(point => ({ ...point, num: point.num + 1 })))] } set(path) } - if (props.saved && props.mode !== "disabled") { - props.setSaved(false) - } } } @@ -267,6 +341,67 @@ const FlightPlanMap = props => { return null; }; + const MarkerPopup = ({ marker, i }) => { + return ( +
+ Altitude (feet) + signedFloatValidation(v, marker.alt, (k) => { + let path = props.getters.path + props.setters.path([...path.slice(0, i), { ...marker, alt: k }, ...path.slice(i + 1)]) + })} /> + +
+ ) + } + return (
{ })} - + {props.getters.ugvDrop.lat == null ? null : singlePopup(props.getters.ugvDrop, "ugvDrop")} @@ -368,49 +503,52 @@ const FlightPlanMap = props => { - (marker.num !== 0) && (marker.cmd !== 177))} color="#10336B" decoratorColor="#1d5cc2" /> + marker.cmd !== Commands.jump)} color="#10336B" decoratorColor="#1d5cc2" /> {props.getters.path.map((marker, i) => { - if (marker.num === 0) { - return singlePopup(marker, "home") - } else if (marker.cmd === 177) { - return + + if (marker.cmd === Commands.jump) { + let j = i - 1 + if (!props.getters.path[i - 1].lat) { + while (j >= 0 && !props.getters.path[j].lat) { + j-- + } + } + return ( + <> + + {popup({...marker, lng: (props.getters.path[j].lng + props.getters.path[marker.p1 - 1].lng)/2, lat: (props.getters.path[j].lat + props.getters.path[marker.p1 - 1].lat)/2}, marker.num, "jump", ( +
+ Jump from {i} to {marker.p1} + +
+ ), true)} + + ) + } else if (marker.cmd === Commands.unlimLoiter) { + return popup(marker, marker.num, "unlim", ( +
+ Unlimited Loiter Point + +
+ ), true) + } else if (marker.cmd === Commands.turnLoiter) { + return popup(marker, marker.num, "turn", ( +
+ Turn Loiter + +
+ ), true) + } else if (marker.cmd === Commands.timeLoiter) { + return popup(marker, marker.num, "time", ( +
+ Time Loiter + +
+ ), true) } return popup(marker, marker.num, "path", ( -
- Altitude (feet) - { - let path = props.getters.path; - if (!Number.isNaN(Number(v)) && v.length > 0) { - if (v.endsWith(".")) { - props.setters.path([...path.slice(0, i), { ...marker, alt: null }, ...path.slice(i + 1)]) - } else { - props.setters.path([...path.slice(0, i), { ...marker, alt: Number(v) }, ...path.slice(i + 1)]) - } - return v - } else if (v.substring(0, v.length - 1).endsWith(".")) { - return v.substring(0, v.length - 1) - } else if (v.length === 0) { - props.setters.path([...path.slice(0, i), { ...marker, alt: null }, ...path.slice(i + 1)]) - return v - } else if (v.substring(0, Math.max(v.length - 1, 1)) === "-") { - props.setters.path([...path.slice(0, i), { ...marker, alt: null }, ...path.slice(i + 1)]) - return v.substring(0, Math.max(v.length - 1, 1)) - } else if (Number.isNaN(parseFloat(v))) { - return "" - } - - return marker.altitude - }} /> - -
+ ), true) })}
@@ -421,4 +559,4 @@ const FlightPlanMap = props => { ) } -export default FlightPlanMap +export default FlightPlanMap \ No newline at end of file diff --git a/client/src/components/UIElements/Button.js b/client/src/components/UIElements/Button.js index 61fdc00a..42644ec3 100644 --- a/client/src/components/UIElements/Button.js +++ b/client/src/components/UIElements/Button.js @@ -8,7 +8,7 @@ import Link from "./Link" import { ReactComponent as RawWarning } from "icons/warning.svg" import { unselectable } from "css.js" -const Button = forwardRef(({ active, onChange, controlled, to, href, careful = false, ...props }, ref) => { +const Button = forwardRef(({ active, disabled, onChange, onClick, controlled, to, href, careful = false, ...props }, ref) => { const [isActive, setActive] = useState(active ?? false) return ( @@ -16,16 +16,18 @@ const Button = forwardRef(({ active, onChange, controlled, to, href, careful = f ref={ref} className="paragraph" active={controlled ? active : isActive} + disabled={disabled} onMouseDown={() => { - if (!controlled) setActive(true) + if (!disabled) setActive(true) }} onMouseUp={() => { - if (!controlled) + if (!disabled) setTimeout(() => { if (!careful) setActive(false) if (ref?.current) setActive(false) if (onChange) onChange() - }, 100) + if (onClick) onClick() + }, 50) }} to={to} href={href} @@ -65,9 +67,9 @@ export const StyledButton = styled(Link).attrs(props => ({ ${unselectable} position: relative; box-sizing: border-box; - background: ${props => (props.active ? (props.color ?? blue) : dark)}; + background: ${props => (props.active && !props.disabled ? (props.color ?? blue) : dark)}; transition: background-color 0.1s ease; - color: ${props => (props.active ? dark : (props.color ?? blue))} !important; + color: ${props => (props.active ? dark : (!props.disabled ? props.color ?? blue : "grey"))} !important; text-decoration: none !important; display: flex; justify-content: center; @@ -75,7 +77,7 @@ export const StyledButton = styled(Link).attrs(props => ({ text-align: center; padding-top: ${props => (props.large ? "1rem" : "0.3rem")}; padding-bottom: ${props => (props.large ? "1rem" : "0.3rem")}; - cursor: pointer; + cursor: ${props => props.disabled ? "not-allowed" : "pointer"}; ::after { content: ""; @@ -84,7 +86,7 @@ export const StyledButton = styled(Link).attrs(props => ({ right: 0; bottom: 0; height: 0.25rem; - background: ${props => props.color ?? blue}; + background: ${props => (props.disabled ? "grey" : props.color) ?? blue}; transition: height 0.1s ease; } @@ -94,8 +96,8 @@ export const StyledButton = styled(Link).attrs(props => ({ left: 0; right: 0; bottom: 0; - height: 0.5rem; - background: ${props => props.color ?? blue}; + height: ${props => !props.disabled ? "0.5rem" : "0.25rem"}; + background: ${props => (props.disabled ? "grey" : props.color) ?? blue}; } ` diff --git a/client/src/components/UIElements/Switch.js b/client/src/components/UIElements/Switch.js new file mode 100644 index 00000000..2767e246 --- /dev/null +++ b/client/src/components/UIElements/Switch.js @@ -0,0 +1,9 @@ +import React, { useState } from "react" + +const Switch = (props) => { + return ( + <> + ) +} + +export default Switch \ No newline at end of file diff --git a/client/src/pages/FlightData/index.js b/client/src/pages/FlightData/index.js index 108e9280..4affde0e 100644 --- a/client/src/pages/FlightData/index.js +++ b/client/src/pages/FlightData/index.js @@ -25,7 +25,9 @@ TODO: Display list highlighting (and vice versa) */ const FlightData = () => { - const [mode, setMode] = useState("disabled") + const [mode, setMode] = useState("waypoint") + const [placementMode, setPlacementMode] = useState("push") + const [previousMode, setPreviousMode] = useState("disabled") const [saved, setSaved] = useState(true) const [defaultAlt, setDefaultAlt] = useState(100) @@ -87,9 +89,13 @@ const FlightData = () => { obstacles: "Obstacles", offAxis: "Off Axis ODLC", searchGrid: "ODLC Search Grid", + loiter: "loiter", path: "Mission Path", + unlim: "Unlimited Loiter", + turn: "Turn Loiter", + time: "Time Loiter", + jump: "Jump", uav: "UAV", - ugv: "UGV", home: "Home Waypoint" } @@ -131,9 +137,13 @@ const FlightData = () => { getters={getters} setters={setters} mode={mode} + setMode={setMode} + previousMode={previousMode} + setPreviousMode={setPreviousMode} + placementMode={placementMode} + setPlacementMode={setPlacementMode} saved={saved} setSaved={setSaved} - setMode={setMode} tabName={"Map"} /> @@ -144,6 +154,10 @@ const FlightData = () => { setters={setters} mode={mode} saved={saved} + previousMode={previousMode} + setPreviousMode={setPreviousMode} + placementMode={placementMode} + setPlacementMode={setPlacementMode} setSaved={setSaved} setMode={setMode} /> diff --git a/client/src/pages/FlightData/tabs/FlightPlan/FlightPlanToolbar.js b/client/src/pages/FlightData/tabs/FlightPlan/FlightPlanToolbar.js index 1fa7dafe..e4f71916 100644 --- a/client/src/pages/FlightData/tabs/FlightPlan/FlightPlanToolbar.js +++ b/client/src/pages/FlightData/tabs/FlightPlan/FlightPlanToolbar.js @@ -1,9 +1,10 @@ import React, { useEffect, useState } from "react" -import { Box, Button, RadioList } from "components/UIElements" +import { Box, Button, Dropdown, RadioList } from "components/UIElements" import { red } from "theme/Colors" import { httppost } from "backend" import { Modal, ModalHeader, ModalBody } from "components/Containers" +import Commands from "commands" const FlightPlanToolbar = props => { const [open, setOpen] = useState(false) @@ -25,7 +26,7 @@ const FlightPlanToolbar = props => { props.setSaved(true) props.setters.pathSave(path) - httppost("/uav/commands/generate", { "waypoints": path.map(waypoint => ({ ...waypoint, lon: waypoint.lng, alt: waypoint.alt / 3.281 })) }) // convert feet to meters for altitude + httppost("/uav/commands/generate", { "waypoints": path.map(waypoint => ({ ...waypoint, lat: waypoint.lat ?? 0.0, lon: waypoint.lng ?? 0.0, alt: (waypoint.alt ?? 0.0) / 3.281 })) }) // convert feet to meters for altitude } return ( @@ -43,10 +44,21 @@ const FlightPlanToolbar = props => { savePath(path) }}>Set as default ({props.getters.defaultAlt} ft) - props.setMode(event.target.value)} name="pointMode"> - Don't make points - Push Mode - Insertion Mode +
+ { + props.setPlacementMode(v) + }}> + Disable + Push + Insert + +
+ { props.setMode(event.target.value); console.log(event.target.value) }} name="pointMode"> + Waypoints + Add Jump + Unlimited Loiter + Turn Loiter + Time Loiter
Default Altitude (ft): @@ -73,42 +85,51 @@ const FlightPlanToolbar = props => { return props.getters.defaultAlt }} />
- {props.getters.path.length === 0 ? null : - - } - {props.saved == true ? null : ( -
- +
+ - + savePath(props.getters.path) + }}>Click to save + + {props.saved ? null : You have unsaved points! -
- )} - {props.getters.path.length === 0 ? ( - - ) : null} + } +
+ +
+ + +
) }