From 50b6f8731978120965e3b4604fd300cb5b5a39d9 Mon Sep 17 00:00:00 2001 From: rSchwartz1 <55598798+rSchwartz1@users.noreply.github.com> Date: Fri, 13 Mar 2020 12:55:39 -0700 Subject: [PATCH] Add KeepIn and KeepOut geofence boxes ## Why is this being changed? This is being changed because of Northrop's requirement of having a KeepIn and KeepOut box for all vehicles. The KeepIn area is an area that all vehicles must stay inside of, and will be about the size of the mission field/runway. The KeepOut area is an area that all vehicles must stay outside of, and will be the location of all student/Northrop observers. ## What was changed to address this problem? In order to address the problem, two geofence buttons were created inside of the missionWindow UI tah tallows the GCS operator to dynamically create the two boxes individually, separating them by color. ## How was this change tested? This change was tested with npm test. --- src/common/struct/Vehicle.ts | 5 - src/renderer/mainWindow/map/MapContainer.tsx | 2 +- src/renderer/missionWindow/MissionWindow.tsx | 172 +++++++++++++----- .../extra/CreateBoundingBoxButton.tsx | 9 +- src/renderer/missionWindow/mission.css | 25 ++- .../missionWindow/parameter/VTOLSearch.tsx | 2 +- src/types/message.ts | 58 +++--- 7 files changed, 179 insertions(+), 94 deletions(-) diff --git a/src/common/struct/Vehicle.ts b/src/common/struct/Vehicle.ts index 1b3e9e2e..dfe355a7 100644 --- a/src/common/struct/Vehicle.ts +++ b/src/common/struct/Vehicle.ts @@ -260,11 +260,6 @@ export default class Vehicle { this.sendMessage({ type: 'start', jobType, - geofence: { - topLeft: [0, 0], - botRight: [0, 0], - keepOut: true, - }, }); this.updateEventHandler.addHandler('status', (value): boolean => { diff --git a/src/renderer/mainWindow/map/MapContainer.tsx b/src/renderer/mainWindow/map/MapContainer.tsx index 0e4e5b8f..38e45cf1 100644 --- a/src/renderer/mainWindow/map/MapContainer.tsx +++ b/src/renderer/mainWindow/map/MapContainer.tsx @@ -318,7 +318,7 @@ export default class MapContainer extends Component { boundingBoxes.forEach((boundingBox): void => { if (!newBoundingBoxes[boundingBox.name]) { newBoundingBoxes[boundingBox.name] = { - color: boundingBox.color || 'red', + color: boundingBox.color || 'black', bounds: boundingBox.bounds || (viewport.center && { top: viewport.center[0], bottom: viewport.center[0], diff --git a/src/renderer/missionWindow/MissionWindow.tsx b/src/renderer/missionWindow/MissionWindow.tsx index c625ce73..2ffbec41 100644 --- a/src/renderer/missionWindow/MissionWindow.tsx +++ b/src/renderer/missionWindow/MissionWindow.tsx @@ -54,23 +54,22 @@ const title: { [missionName in MissionInformation.MissionName]: string } = { uuvRescue: 'UUV Rescue', }; -type GeofenceChecklistType = 'geofenceTop' | 'geofenceLeft' | 'geofenceRight' | 'geofenceBottom'; +type GeofenceChecklistType = 'geofenceKeepInTop' | 'geofenceKeepInLeft' | 'geofenceKeepInRight' | 'geofenceKeepInBottom' | 'geofenceKeepOutTop' | 'geofenceKeepOutLeft' | 'geofenceKeepOutRight' | 'geofenceKeepOutBottom'; const checklistCache: { [check in GeofenceChecklistType ]: number | undefined} = { - geofenceTop: undefined, - geofenceLeft: undefined, - geofenceRight: undefined, - geofenceBottom: undefined, + geofenceKeepInTop: undefined, + geofenceKeepInLeft: undefined, + geofenceKeepInRight: undefined, + geofenceKeepInBottom: undefined, + geofenceKeepOutTop: undefined, + geofenceKeepOutLeft: undefined, + geofenceKeepOutRight: undefined, + geofenceKeepOutBottom: undefined, }; -type Locked = { - geofence: boolean; -}; - -const lockedCache: Locked = { - geofence: true, -}; +type Locked = { geofenceKeepIn: boolean, geofenceKeepOut: boolean }; +const lockedCache: Locked = { geofenceKeepIn: true, geofenceKeepOut: true }; interface State { /** @@ -223,50 +222,98 @@ export default class MissionWindow extends Component { const name = event.target.name as GeofenceChecklistType; const value = parseInt(event.target.value, 10) || 0; switch (name) { - case 'geofenceTop': + case 'geofenceKeepInTop': + ipc.postUpdateBoundingBoxes(true, { + name: 'Geofence Keep In', + bounds: { + top: value, + bottom: checklist.geofenceKeepInBottom as number, + left: checklist.geofenceKeepInLeft as number, + right: checklist.geofenceKeepInRight as number, + }, + }); + break; + + case 'geofenceKeepInLeft': + ipc.postUpdateBoundingBoxes(true, { + name: 'Geofence Keep In', + bounds: { + top: checklist.geofenceKeepInTop as number, + bottom: checklist.geofenceKeepInBottom as number, + left: value, + right: checklist.geofenceKeepInRight as number, + }, + }); + break; + + case 'geofenceKeepInRight': + ipc.postUpdateBoundingBoxes(true, { + name: 'Geofence Keep In', + bounds: { + top: checklist.geofenceKeepInTop as number, + bottom: checklist.geofenceKeepInBottom as number, + left: checklist.geofenceKeepInLeft as number, + right: value, + }, + }); + break; + + case 'geofenceKeepInBottom': ipc.postUpdateBoundingBoxes(true, { - name: 'Geofencing', + name: 'Geofence Keep In', + bounds: { + top: checklist.geofenceKeepInTop as number, + bottom: value, + left: checklist.geofenceKeepInLeft as number, + right: checklist.geofenceKeepInRight as number, + }, + }); + break; + + case 'geofenceKeepOutTop': + ipc.postUpdateBoundingBoxes(true, { + name: 'Geofence Keep Out', bounds: { top: value, - bottom: checklist.geofenceBottom as number, - left: checklist.geofenceLeft as number, - right: checklist.geofenceRight as number, + bottom: checklist.geofenceKeepOutBottom as number, + left: checklist.geofenceKeepOutLeft as number, + right: checklist.geofenceKeepOutRight as number, }, }); break; - case 'geofenceLeft': + case 'geofenceKeepOutLeft': ipc.postUpdateBoundingBoxes(true, { - name: 'Geofencing', + name: 'Geofence Keep Out', bounds: { - top: checklist.geofenceTop as number, - bottom: checklist.geofenceBottom as number, + top: checklist.geofenceKeepOutTop as number, + bottom: checklist.geofenceKeepOutBottom as number, left: value, - right: checklist.geofenceRight as number, + right: checklist.geofenceKeepOutRight as number, }, }); break; - case 'geofenceRight': + case 'geofenceKeepOutRight': ipc.postUpdateBoundingBoxes(true, { - name: 'Geofencing', + name: 'Geofence Keep Out', bounds: { - top: checklist.geofenceTop as number, - bottom: checklist.geofenceBottom as number, - left: checklist.geofenceLeft as number, + top: checklist.geofenceKeepOutTop as number, + bottom: checklist.geofenceKeepOutBottom as number, + left: checklist.geofenceKeepOutLeft as number, right: value, }, }); break; - case 'geofenceBottom': + case 'geofenceKeepOutBottom': ipc.postUpdateBoundingBoxes(true, { - name: 'Geofencing', + name: 'Geofence Keep Out', bounds: { - top: checklist.geofenceTop as number, + top: checklist.geofenceKeepOutTop as number, bottom: value, - left: checklist.geofenceLeft as number, - right: checklist.geofenceRight as number, + left: checklist.geofenceKeepOutLeft as number, + right: checklist.geofenceKeepOutRight as number, }, }); break; @@ -368,11 +415,18 @@ export default class MissionWindow extends Component { boundingBoxes.forEach((boxpoint): void => { switch (boxpoint.name) { - case 'Geofencing': - checks.geofenceTop = boxpoint.bounds.top; - checks.geofenceRight = boxpoint.bounds.right; - checks.geofenceLeft = boxpoint.bounds.left; - checks.geofenceBottom = boxpoint.bounds.bottom; + case 'Geofence Keep In': + checks.geofenceKeepInTop = boxpoint.bounds.top; + checks.geofenceKeepInRight = boxpoint.bounds.right; + checks.geofenceKeepInLeft = boxpoint.bounds.left; + checks.geofenceKeepInBottom = boxpoint.bounds.bottom; + break; + + case 'Geofence Keep Out': + checks.geofenceKeepOutTop = boxpoint.bounds.top; + checks.geofenceKeepOutRight = boxpoint.bounds.right; + checks.geofenceKeepOutLeft = boxpoint.bounds.left; + checks.geofenceKeepOutBottom = boxpoint.bounds.bottom; break; default: break; @@ -447,8 +501,11 @@ export default class MissionWindow extends Component { private unlockParameterInputs(waypointType: string): void { const { locked: newLocked } = this.state; - if (waypointType === 'geofence') { - newLocked.geofence = false; + if (waypointType === 'geofenceKeepIn') { + newLocked.geofenceKeepIn = false; + } + if (waypointType === 'geofenceKeepOut') { + newLocked.geofenceKeepOut = false; } this.setState({ locked: newLocked }); @@ -486,10 +543,14 @@ export default class MissionWindow extends Component { */ const unlockStartMissionButton = information[missionName].parameters !== undefined - && checklist.geofenceTop !== undefined - && checklist.geofenceBottom !== undefined - && checklist.geofenceLeft !== undefined - && checklist.geofenceRight !== undefined; + && checklist.geofenceKeepInTop !== undefined + && checklist.geofenceKeepInBottom !== undefined + && checklist.geofenceKeepInLeft !== undefined + && checklist.geofenceKeepInRight !== undefined + && checklist.geofenceKeepOutTop !== undefined + && checklist.geofenceKeepOutBottom !== undefined + && checklist.geofenceKeepOutLeft !== undefined + && checklist.geofenceKeepOutRight !== undefined; return (
@@ -516,16 +577,27 @@ export default class MissionWindow extends Component {

Options

-
-

Geofencing

- +
+

Geofence Keep In

+ +
+ +
+ +
+ + +
+
+

Geofence Keep Out

+
- +
- +
- - + +
{status === 'ready' && } diff --git a/src/renderer/missionWindow/extra/CreateBoundingBoxButton.tsx b/src/renderer/missionWindow/extra/CreateBoundingBoxButton.tsx index b0d76365..d47f2bc2 100644 --- a/src/renderer/missionWindow/extra/CreateBoundingBoxButton.tsx +++ b/src/renderer/missionWindow/extra/CreateBoundingBoxButton.tsx @@ -13,6 +13,11 @@ export interface CreateBoundingBoxButtonProps extends ThemeProps{ * Name of the box itself, when it shows up on the map. */ value: string; + + /** + * Color of the box as it appears on the map. + */ + color: string; } export default class CreateBoundingBoxButton extends PureComponent { @@ -23,9 +28,9 @@ export default class CreateBoundingBoxButton extends PureComponent {
- +
); } diff --git a/src/types/message.ts b/src/types/message.ts index 1e518a76..e8e4b9d0 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -28,28 +28,6 @@ export interface StartMessage extends MessageBase { * Name of job to perform. */ jobType: JobType; - - /** - * Geofencing coordinates in the form of a rectangle, and geofence type sent with start message. - */ - geofence: { - - /** - * Top Left coordinate of geofencing rectangle, in form of latitude, longitude. - */ - topLeft: [number, number]; - - /** - * Bottom right coordinate of geofencing rectangle, in form of latitude, longitude. - */ - botRight: [number, number]; - - /** - * Boolean to specify whether geofence is keep out type (true), or keep in type (false). - */ - keepOut: boolean; - }; - } /** @@ -110,6 +88,34 @@ function isStopMessage(message: Message): boolean { return message.type === 'stop'; } +export interface GeofenceMessage extends MessageBase { + type: 'geofence'; + + /** + * Geofencing coordinates in the form of a polygon. + */ + geofence: { + /** + * geofence.keepOut and geofence.keepIn are defined as an array of tuples( [[number, number]] ). + * Each tuple consists of [ [latitude, longitude] ]. + */ + keepOut: [number, number][], + keepIn: [number, number][] + } +} + +/** + * Type guard for Geofence Message. + */ +function isGeofenceMessage(message: Message): boolean { + return message.type === 'geofence' + && message.geofence !== undefined + && Array.isArray(message.geofence.keepIn) + && Array.isArray(message.geofence.keepOut) + && message.geofence.keepIn.every((pair) => pair.length === 2 && typeof pair[0] === 'number' && typeof pair[1] === 'number') + && message.geofence.keepOut.every((pair) => pair.length === 2 && typeof pair[0] === 'number' && typeof pair[1] === 'number'); +} + export interface ConnectionAckMessage extends MessageBase { type: 'connectionAck'; } @@ -280,10 +286,10 @@ export function isBadMessage(message: Message): boolean { /** * All types of messages sent to and from the GCS. */ -export type Message = StartMessage | AddMissionMessage | PauseMessage | ResumeMessage | StopMessage -| ConnectionAckMessage | UpdateMessage | POIMessage | CompleteMessage | ConnectMessage -| AcknowledgementMessage | BadMessage; +export type Message = StartMessage | AddMissionMessage | PauseMessage | ResumeMessage | StopMessage +| GeofenceMessage | ConnectionAckMessage | UpdateMessage | POIMessage | CompleteMessage +| ConnectMessage | AcknowledgementMessage | BadMessage; /** * Simply checks if the message has a valid type field. This is different from the type * guards from types/missionInformation and types/task. @@ -298,6 +304,7 @@ export function isMessage(message: { [key: string]: any }): boolean { 'pause', 'resume', 'stop', + 'geofence', 'connectionAck', 'update', 'poi', @@ -348,6 +355,7 @@ export const TypeGuard = { isPauseMessage, isResumeMessage, isStopMessage, + isGeofenceMessage, isConnectionAcknowledgementMessage, isUpdateMessage, isPOIMessage,