Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gcs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"name": "Avis-Drone-Labs"
},
"private": true,
"version": "0.2.6-alpha",
"version": "0.2.7-alpha",
"license": "GPL-3.0-only",
"homepage": "https://fgcs.projectfalcon.uk",
"githubLink": "https://github.com/Avis-Drone-Labs/FGCS",
Expand Down
20 changes: 17 additions & 3 deletions gcs/src/components/missions/missionItemsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@
*/

import { Table } from "@mantine/core"
import React from "react"
import React, { useMemo } from "react"
import { isGlobalFrameHomeCommand } from "../../helpers/filterMissions"
import { buildMissionWaypointLegMetrics } from "../../helpers/missionWaypointMetrics"
import MissionItemsTableRow from "./missionItemsTableRow"

// Redux
import { useSelector } from "react-redux"
import { selectDrawingMissionItems } from "../../redux/slices/missionSlice"
import {
selectDrawingMissionItems,
selectPlannedHomePosition,
} from "../../redux/slices/missionSlice"

function MissionItemsTableNonMemo({ tableSectionHeight }) {
const missionItems = useSelector(selectDrawingMissionItems)
const plannedHomePosition = useSelector(selectPlannedHomePosition)

const rowMetricsByIdx = useMemo(
() => buildMissionWaypointLegMetrics(missionItems, plannedHomePosition),
[missionItems, plannedHomePosition],
)

return (
<Table.ScrollContainer maxHeight={tableSectionHeight}>
Expand All @@ -28,20 +38,23 @@ function MissionItemsTableNonMemo({ tableSectionHeight }) {
<Table.Th>Lat</Table.Th>
<Table.Th>Lng</Table.Th>
<Table.Th>Alt</Table.Th>
<Table.Th>Distance</Table.Th>
<Table.Th>Gradient</Table.Th>
<Table.Th>Frame</Table.Th>
<Table.Th></Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{missionItems.map((missionItem, idx) => {
// Skip home location
if (idx === 0 && isGlobalFrameHomeCommand(missionItem)) {
return null
}

return (
<MissionItemsTableRow
key={missionItem.id}
missionItemIndex={idx}
rowMetrics={rowMetricsByIdx[idx]}
/>
)
})}
Expand All @@ -54,6 +67,7 @@ function MissionItemsTableNonMemo({ tableSectionHeight }) {
function propsAreEqual(prev, next) {
return JSON.stringify(prev) === JSON.stringify(next)
}

const MissionItemsTable = React.memo(MissionItemsTableNonMemo, propsAreEqual)

export default MissionItemsTable
12 changes: 11 additions & 1 deletion gcs/src/components/missions/missionItemsTableRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ import {

const coordsFractionDigits = 9

export default function MissionItemsTableRow({ missionItemIndex }) {
function formatMetric(value, suffix) {
if (!Number.isFinite(value)) {
return "-"
}

return `${value.toFixed(2)}${suffix}`
}

export default function MissionItemsTableRow({ missionItemIndex, rowMetrics }) {
const dispatch = useDispatch()
const aircraftType = useSelector(selectAircraftType)
const missionItem = useSelector(
Expand Down Expand Up @@ -166,6 +174,8 @@ export default function MissionItemsTableRow({ missionItemIndex }) {
hideControls
/>
</TableTd>
<TableTd>{formatMetric(rowMetrics?.distanceMeters, "m")}</TableTd>
<TableTd>{formatMetric(rowMetrics?.gradientPercent, "%")}</TableTd>
<TableTd>
<Select
data={MAV_FRAME_DROPDOWN_DATA}
Expand Down
103 changes: 28 additions & 75 deletions gcs/src/components/missions/missionStatistics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
filterMissionItems,
isGlobalFrameHomeCommand,
} from "../../helpers/filterMissions"
import { buildMissionWaypointLegMetrics } from "../../helpers/missionWaypointMetrics"

// Redux
import { useSelector } from "react-redux"
Expand All @@ -28,96 +29,48 @@ function calculateMaxAltitude(missionItems) {
}

function calculateMaxDistanceBetweenWaypoints(missionItems, homePosition) {
if (missionItems.length < 2) return 0
let maxDistance = 0
let maxDistancePoints = []
const legMetrics = buildMissionWaypointLegMetrics(missionItems, homePosition)
let maxLeg = null

// Swap the coords of the first position with the home position if it exists
if (
isGlobalFrameHomeCommand(missionItems[0]) &&
homePosition &&
missionItems[1].command === 22
) {
const newFirstPoint = {
...missionItems[1],
x: homePosition.lat,
y: homePosition.lon,
z: homePosition.alt,
for (const leg of legMetrics) {
if (!leg || leg.distanceMeters === null) {
continue
}
missionItems = [newFirstPoint, ...missionItems.slice(1)]
}

// Remove any waypoints without coordinates
missionItems = missionItems.filter((item) => item.x !== 0 && item.y !== 0)

for (let i = 0; i < missionItems.length - 1; i++) {
const item1 = missionItems[i]
const item2 = missionItems[i + 1]
const distanceBetweenPoints = distance(
[intToCoord(item1.y), intToCoord(item1.x)],
[intToCoord(item2.y), intToCoord(item2.x)],
{
units: "meters",
},
)
if (distanceBetweenPoints > maxDistance) {
maxDistance = distanceBetweenPoints
maxDistancePoints = [item1, item2]
if (maxLeg === null || leg.distanceMeters > maxLeg.distanceMeters) {
maxLeg = leg
}
}

// distance to 2dp
maxDistance = Math.round(maxDistance * 100) / 100

return { maxDistance: maxDistance, points: maxDistancePoints }
return {
maxDistance: maxLeg ? Math.round(maxLeg.distanceMeters * 100) / 100 : 0,
points: maxLeg ? [maxLeg.previousWaypoint, maxLeg.currentWaypoint] : [],
}
}

function calculateMaxSlopeGradient(missionItems, homePosition) {
if (missionItems.length < 2) return 0
let maxGradient = 0
let maxDistancePoints = []
const legMetrics = buildMissionWaypointLegMetrics(missionItems, homePosition)
let maxLeg = null

// Swap the coords of the first position with the home position if it exists
if (
isGlobalFrameHomeCommand(missionItems[0]) &&
homePosition &&
missionItems[1].command === 22
) {
const newFirstPoint = {
...missionItems[1],
x: homePosition.lat,
y: homePosition.lon,
z: homePosition.alt,
for (const leg of legMetrics) {
if (!leg || leg.gradientPercent === null) {
continue
}
missionItems = [newFirstPoint, ...missionItems.slice(1)]
}

for (let i = 0; i < missionItems.length - 1; i++) {
const item1 = missionItems[i]
const item2 = missionItems[i + 1]

if (item1.z === undefined || item2.z === undefined) continue

const verticalDistance = Math.abs(item2.z - item1.z)
const horizontalDistance = distance(
[intToCoord(item1.y), intToCoord(item1.x)],
[intToCoord(item2.y), intToCoord(item2.x)],
{ units: "meters" },
)

if (horizontalDistance === 0) continue // Avoid division by zero

const gradient = (verticalDistance / horizontalDistance) * 100 // Convert to percentage

if (gradient > maxGradient) {
maxGradient = gradient
maxDistancePoints = [item1, item2]
if (
maxLeg === null ||
Math.abs(leg.gradientPercent) > Math.abs(maxLeg.gradientPercent)
) {
maxLeg = leg
}
}

maxGradient = Math.round(maxGradient * 100) / 100 // Round to two decimal places

return { maxGradient: maxGradient, points: maxDistancePoints }
return {
maxGradient: maxLeg
? Math.round(Math.abs(maxLeg.gradientPercent) * 100) / 100
: 0,
points: maxLeg ? [maxLeg.previousWaypoint, maxLeg.currentWaypoint] : [],
}
}

function calculateTotalDistance(missionItems, homePosition) {
Expand Down
159 changes: 159 additions & 0 deletions gcs/src/helpers/missionWaypointMetrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { distance } from "@turf/turf"
import { intToCoord } from "./dataFormatters"
import { isGlobalFrameHomeCommand } from "./filterMissions"

const STOP_COMMANDS = [20, 21, 189]
const RTL_COMMAND = 20

function toFiniteNumber(value) {
const parsedValue = Number(value)
return Number.isFinite(parsedValue) ? parsedValue : null
}

function buildHomeWaypoint(homePosition) {
const homeLat = toFiniteNumber(homePosition?.lat)
const homeLon = toFiniteNumber(homePosition?.lon)

if (homeLat === null || homeLon === null) {
return null
}

// TODO: Allow home waypoint altitude to be used for absolute altitude
// calculations in the future.
return {
isHome: true,
seq: "Home",
x: homeLat,
y: homeLon,
z: 0,
lat: intToCoord(homeLat),
lon: intToCoord(homeLon),
altitude: 0,
}
Comment thread
1Blademaster marked this conversation as resolved.
}

function buildResolvedWaypoint(missionItem) {
const x = toFiniteNumber(missionItem?.x)
const y = toFiniteNumber(missionItem?.y)
const z = toFiniteNumber(missionItem?.z)

const hasPosition = x !== null && y !== null && x !== 0 && y !== 0

return {
seq: missionItem?.seq,
isHome: false,
x,
y,
z,
lat: hasPosition ? intToCoord(x) : null,
lon: hasPosition ? intToCoord(y) : null,
altitude: hasPosition ? z : null,
}
}

function getDisplayPointLabel(point) {
if (!point) {
return null
}

return point.isHome ? "Home" : point.seq
}

export function buildMissionWaypointLegMetrics(missionItems, homePosition) {
if (!Array.isArray(missionItems) || missionItems.length === 0) {
return []
}

const metricsByIndex = []
const homeWaypoint = buildHomeWaypoint(homePosition)
const stopCommandItem = [...missionItems]
.filter((item) => STOP_COMMANDS.includes(item.command))
.sort((a, b) => a.seq - b.seq)
.at(0)

let previousWaypoint = homeWaypoint
let previousAltitude = homeWaypoint ? homeWaypoint.altitude : null

Comment thread
1Blademaster marked this conversation as resolved.
for (let idx = 0; idx < missionItems.length; idx++) {
const missionItem = missionItems[idx]

if (stopCommandItem && missionItem.seq > stopCommandItem.seq) {
continue
}

if (idx === 0 && isGlobalFrameHomeCommand(missionItem)) {
continue
}

const resolvedWaypoint = buildResolvedWaypoint(missionItem)
const isRtlCommand = missionItem.command === RTL_COMMAND
const currentWaypoint =
isRtlCommand && homeWaypoint
? homeWaypoint
: resolvedWaypoint.lat !== null && resolvedWaypoint.lon !== null
? resolvedWaypoint
: previousWaypoint
const currentAltitude =
isRtlCommand && homeWaypoint
? homeWaypoint.altitude
: resolvedWaypoint.altitude !== null
? resolvedWaypoint.altitude
: previousAltitude
Comment thread
1Blademaster marked this conversation as resolved.

let distanceMeters = null
let gradientPercent = null

if (
previousWaypoint?.lat !== null &&
previousWaypoint?.lon !== null &&
currentWaypoint?.lat !== null &&
currentWaypoint?.lon !== null
) {
distanceMeters = distance(
[previousWaypoint.lon, previousWaypoint.lat],
[currentWaypoint.lon, currentWaypoint.lat],
{ units: "meters" },
)
}

if (
previousWaypoint?.lat !== null &&
previousWaypoint?.lon !== null &&
previousAltitude !== null &&
currentAltitude !== null
) {
const horizontalDistanceMeters =
distanceMeters ??
distance(
[previousWaypoint.lon, previousWaypoint.lat],
[currentWaypoint.lon, currentWaypoint.lat],
{ units: "meters" },
)

if (horizontalDistanceMeters > 0) {
gradientPercent =
((currentAltitude - previousAltitude) / horizontalDistanceMeters) *
100
}
}

metricsByIndex[idx] = {
currentWaypoint,
previousWaypoint,
distanceMeters,
gradientPercent,
previousWaypointLabel: getDisplayPointLabel(previousWaypoint),
currentWaypointLabel: getDisplayPointLabel(currentWaypoint),
}

if (resolvedWaypoint.lat !== null && resolvedWaypoint.lon !== null) {
previousWaypoint = resolvedWaypoint
}

if (resolvedWaypoint.altitude !== null) {
previousAltitude = resolvedWaypoint.altitude
}
}

return metricsByIndex
}
Loading
Loading