Position deviation
The drift from a commanded position, as a circle that grows with the distance, with caution and alarm rings.
On this page
Code
import {
PositionDeviation,
PositionDeviationOrientation,
PositionDeviationPriority,
Readout,
ReadoutSize,
ReadoutValueType,
} from "@bosunkit/instruments";
import { useEffect, useState, type CSSProperties, type Dispatch, type ReactElement, type SetStateAction } from "react";
export const description: string =
"Holding station off the fuel dock on a live feed: the wind pushes the vessel off its position and the helm brings " +
"it back, the circle crosses the caution and then the alarm ring and turns their color, and the last eight " +
"positions trail behind, on a north-up card beside a heading-up one.";
interface TrailPoint {
distanceM: number;
bearingDeg: number;
}
const CAUTION_LIMIT: number = 12;
const ALARM_LIMIT: number = 20;
const TRAIL_POINTS: number = 8;
const TRAIL_SPACING_SECONDS: number = 2;
const PANEL: CSSProperties = {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "center",
gap: 24,
width: "100%",
maxWidth: 660,
};
const FIGURE: CSSProperties = { display: "flex", flexDirection: "column", alignItems: "center", gap: 6, margin: 0 };
const CAPTION: CSSProperties = { fontSize: 13, color: "var(--text-dim)", textAlign: "center", maxWidth: 220 };
const COLUMN: CSSProperties = { display: "flex", flexDirection: "column", gap: 14 };
/** Distance in meters from the vessel to the held position: the drift builds over forty seconds and is corrected. */
function deviationAt(time: number): number {
return 3 + 19 * ((1 - Math.cos((2 * Math.PI * time) / 40)) / 2);
}
/** Bearing in degrees from the vessel to the held position, swinging as the wind shifts. */
function bearingAt(time: number): number {
return 200 + 25 * Math.sin(time / 9);
}
function priorityFor(deviation: number): PositionDeviationPriority {
if (deviation >= ALARM_LIMIT) {
return PositionDeviationPriority.alarm;
}
if (deviation >= CAUTION_LIMIT) {
return PositionDeviationPriority.caution;
}
return PositionDeviationPriority.regular;
}
export default function Showcase(): ReactElement {
const [second, setSecond]: [number, Dispatch<SetStateAction<number>>] = useState<number>(0);
useEffect((): (() => void) => {
const interval: number = window.setInterval((): void => {
setSecond((previous: number): number => previous + 1);
}, 1000);
return (): void => {
window.clearInterval(interval);
};
}, []);
const deviation: number = deviationAt(second);
const bearing: number = bearingAt(second);
const heading: number = 112 + 6 * Math.sin(second / 5);
const trail: TrailPoint[] = Array.from({ length: TRAIL_POINTS }, (_: unknown, index: number): TrailPoint => {
const time: number = second - (TRAIL_POINTS - 1 - index) * TRAIL_SPACING_SECONDS;
return { distanceM: deviationAt(time), bearingDeg: bearingAt(time) };
});
return (
<div style={PANEL}>
<figure style={FIGURE}>
<div style={{ width: 250, height: 250 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.northUp}
priority={priorityFor(deviation)}
heading={heading}
deviation={deviation}
setpointBearing={bearing}
cautionLimit={CAUTION_LIMIT}
alarmLimit={ALARM_LIMIT}
showLabels
trail={trail}
/>
</div>
<figcaption style={CAPTION}>North up, both limits, the trail and the color of the ring it is past</figcaption>
</figure>
<figure style={FIGURE}>
<div style={{ width: 170, height: 170 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.headingUp}
priority={PositionDeviationPriority.enhanced}
heading={heading}
deviation={deviation}
setpointBearing={bearing}
cautionLimit={CAUTION_LIMIT}
alarmLimit={ALARM_LIMIT}
hasAlarmLimit={false}
showLabels={false}
/>
</div>
<figcaption style={CAPTION}>Heading up at the helm, scaled to the caution ring</figcaption>
</figure>
<div style={COLUMN}>
<Readout label="Off station" value={deviation} unit="m" src="GPS" size={ReadoutSize.large} />
<Readout label="Bearing to station" value={bearing} hasDegree />
<Readout
label="Caution, alarm"
valueType={ReadoutValueType.text}
value={`${String(CAUTION_LIMIT)} m, ${String(ALARM_LIMIT)} m`}
/>
</div>
</div>
);
}
The position deviation instrument shows how far the vessel has drifted from a position it is holding, as in station
keeping. A filled circle grows with the distance, a line and a dot point at the setpoint, and a dotted caution ring
and a solid alarm ring mark the limits, so the circle crosses a ring as the drift grows. trail adds the recent
positions. It ports obc-position-deviation, and the trail is a house extension; the overview
lists the other instruments.
Examples
North up
Code
import { PositionDeviation, PositionDeviationOrientation, PositionDeviationPriority } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string = "A drift of 8 meters, inside the caution limit, with north at the top.";
export default function NorthUp(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.northUp}
priority={PositionDeviationPriority.regular}
heading={47}
deviation={8}
setpointBearing={225}
cautionLimit={12}
alarmLimit={20}
/>
</div>
);
}
Heading up
Code
import { PositionDeviation, PositionDeviationOrientation, PositionDeviationPriority } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"A drift of 14 meters, past the caution limit, with the card turned so the heading arrow points up.";
export default function HeadingUp(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.headingUp}
priority={PositionDeviationPriority.caution}
heading={47}
deviation={14}
setpointBearing={225}
cautionLimit={12}
alarmLimit={20}
/>
</div>
);
}
With trail
Code
import { PositionDeviation, PositionDeviationOrientation, PositionDeviationPriority } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"A drift of 18 meters, near the alarm limit, with the last five positions as a fading trail.";
export default function WithTrail(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.northUp}
priority={PositionDeviationPriority.alarm}
heading={47}
deviation={18}
setpointBearing={205}
cautionLimit={12}
alarmLimit={20}
trail={[
{ distanceM: 4, bearingDeg: 335 },
{ distanceM: 7, bearingDeg: 8 },
{ distanceM: 11, bearingDeg: 42 },
{ distanceM: 15, bearingDeg: 73 },
{ distanceM: 18, bearingDeg: 101 },
]}
/>
</div>
);
}
No reading
Code
import { PositionDeviation, PositionDeviationOrientation } from "@bosunkit/instruments";
import type { ReactElement } from "react";
export const description: string =
"A deviation, bearing and heading that are not numbers draw no deviation circle and no setpoint line.";
export default function NoReading(): ReactElement {
return (
<div style={{ width: 200, height: 200 }}>
<PositionDeviation
orientation={PositionDeviationOrientation.northUp}
deviation={Number.NaN}
setpointBearing={Number.NaN}
heading={Number.NaN}
/>
</div>
);
}
Origin and limits
Declare the held position, distance unit and orientation of the view. Keep the current offset, setpoint and trail in the same coordinate frame; a change of origin must update them together. Explain the trail's time window so a long path is not confused with a large instantaneous error.
Caution and alarm rings depict supplied limits. The drawing does not establish an active station-keeping or anchor-watch service and does not emit alerts. Pair it with the numeric deviation, position quality and the monitor's actual state. Missing positions must not reset the vessel silently to the center.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | PositionDeviationOrientation | undefined | PositionDeviationOrientation.northUp | Compass card orientation: northUp (default) keeps north at the top and turns the heading arrow; headingUp keeps
the heading arrow at the top and turns the card. |
| priority | PositionDeviationPriority | undefined | PositionDeviationPriority.regular | Color of the deviation circle: regular (neutral, default), enhanced (blue accent), or caution and alarm,
which tint it with the alert colors. |
| heading | number | undefined | 0 | Heading in degrees (0 = north, clockwise). |
| deviation | number | undefined | 0 | Distance from the current position to the setpoint, in the same unit as the limits. |
| setpointBearing | number | undefined | 0 | Bearing from the current position towards the setpoint in degrees (0 = north, clockwise). |
| cautionLimit | number | undefined | 15 | Deviation at which the dotted caution ring is drawn; hidden when not a positive finite number. |
| alarmLimit | number | undefined | 20 | Deviation at which the solid alarm ring is drawn; hidden when not a positive finite number. |
| hasAlarmLimit | boolean | undefined | true | When false, hides the alarm ring and anchors the scale to the caution ring. |
| showLabels | boolean | undefined | true | When true, shows the N/E/S/W labels on the compass card. |
| trail | { distanceM: number; bearingDeg: number }[] | undefined | [] | PORT EXTENSION: position history, oldest first, rendered as a fading
trail. Upstream obc-position-deviation has no trail property. |
Related
- Compass: the card the orientation modes follow.
- Data integrity: the states a value can be in, and what the crew does.
- Alarms and severity: the priority colors and shapes the alert instruments draw.