Nautical chart symbols
Own vessel, AIS targets and the track made good, drawn in the chart's dynamic layer from their positions and motion.
On this page
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import {
AisTargetSymbol,
OwnVesselSymbol,
TrackSymbol,
type TrackPoint,
} from "@bosunkit/components/nautical-chart-symbols";
import type { ReactElement } from "react";
export const description: string =
"Off the Raz de Sein at 150 units to the mile: own vessel with her heading line, course vector and the last half " +
"hour of track, a sailing vessel closing inside the 0.5 NM and 6 min limits and drawn dangerous, a ferry passing " +
"clear, and a fishing vessel asleep.";
const PIXELS_PER_NM: number = 150;
/** The last 30 minutes of own vessel's track, one position every 5 minutes, oldest first. */
const TRACK: readonly TrackPoint[] = [
{ x: 470, y: 60, ageSeconds: 1800 },
{ x: 458, y: 110, ageSeconds: 1500 },
{ x: 444, y: 160, ageSeconds: 1200 },
{ x: 432, y: 210, ageSeconds: 900 },
{ x: 422, y: 262, ageSeconds: 600 },
{ x: 410, y: 312, ageSeconds: 300 },
{ x: 400, y: 360, ageSeconds: 0 },
];
export default function Showcase(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: "48em" }}>
<NauticalChart staticSvg={null} width={800} height={600}>
<TrackSymbol points={TRACK} />
<AisTargetSymbol
x={250}
y={420}
name="Galatea"
cog={80}
sog={5.5}
cpa={0.3}
tcpa={4}
cpaLimit={0.5}
tcpaLimit={6}
pixelsPerNauticalMile={PIXELS_PER_NM}
/>
<AisTargetSymbol
x={640}
y={520}
name="Iroise Express"
cog={340}
sog={14}
cpa={1.4}
tcpa={9}
cpaLimit={0.5}
tcpaLimit={6}
pixelsPerNauticalMile={PIXELS_PER_NM}
/>
<AisTargetSymbol
x={180}
y={120}
name="Kerlaouen"
state="sleeping"
cog={150}
sog={3}
cpa={2.1}
tcpa={25}
cpaLimit={0.5}
tcpaLimit={6}
pixelsPerNauticalMile={PIXELS_PER_NM}
/>
<OwnVesselSymbol x={400} y={360} heading={196} cog={200} sog={6.4} pixelsPerNauticalMile={PIXELS_PER_NM} />
</NauticalChart>
</div>
);
}
The chart symbols draw what moves on a chart: own vessel, the AIS targets around her and
the track she has made good. They are SVG for the chart's dynamic layer and take positions in the chart's own units
(the pixels of its viewBox) with the chart's scale in units per nautical mile, so the caller projects latitude and
longitude once and the symbols work out their vectors.
Own vessel is the hull the server's chart renderer draws, turned to her heading, with a solid heading line from the bow and a dashed course vector along her course over ground. An AIS target is the IEC 62288 triangle as OpenBridge draws it, turned to its course over ground, with the same dashed vector. Both vectors are six minutes long, a tenth of an hour, so a vector's length in miles is a tenth of the speed in knots. The track fades with age.
Symbols
| Symbol | Draws | Turned by | Vector |
|---|---|---|---|
OwnVesselSymbol | The hull and the heading line; two circles with neither heading nor course | Heading; course over ground without one | Course over ground, six minutes' run, from 0.5 kn |
AisTargetSymbol | The IEC target triangle and its name | Course over ground; unturned triangle without one | Course over ground, six minutes' run, from 0.5 kn; none asleep |
TrackSymbol | A line through past positions | None | None; each segment fades by its age |
A target is dangerous when its CPA is within cpaLimit and its TCPA within tcpaLimit, the limits included, and
the closest approach is ahead: the same rule the AIS target row uses, so the
list and the chart never disagree. A sleeping target that becomes dangerous is drawn activated, with its vector.
Examples
Own vessel
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import { OwnVesselSymbol } from "@bosunkit/components/nautical-chart-symbols";
import type { ReactElement } from "react";
export const description: string =
"Own vessel three ways: heading 196° with a stream setting her to 215° at 6.4 kn, no heading so the hull lies on " +
"the course over ground, and drifting at 0.3 kn with no course vector.";
export default function OwnVessel(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: "40em" }}>
<NauticalChart staticSvg={null} width={600} height={240}>
<OwnVesselSymbol x={110} y={90} heading={196} cog={215} sog={6.4} pixelsPerNauticalMile={150} />
<OwnVesselSymbol x={300} y={90} heading={null} cog={215} sog={6.4} pixelsPerNauticalMile={150} />
<OwnVesselSymbol x={490} y={120} heading={196} cog={40} sog={0.3} pixelsPerNauticalMile={150} />
</NauticalChart>
</div>
);
}
AIS targets
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import { AisTargetSymbol, type AisTargetSymbolProps } from "@bosunkit/components/nautical-chart-symbols";
import type { ReactElement } from "react";
export const description: string =
"Four AIS targets against 0.5 NM and 6 min limits: asleep, activated with its course vector, reporting no course, " +
"and dangerous at a CPA of 0.2 NM in 3 min.";
/** What every target here shares: the chart's scale and the limits. */
const SHARED: Pick<AisTargetSymbolProps, "cpaLimit" | "tcpaLimit" | "pixelsPerNauticalMile"> = {
cpaLimit: 0.5,
tcpaLimit: 6,
pixelsPerNauticalMile: 150,
};
export default function AisTargets(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: "40em" }}>
<NauticalChart staticSvg={null} width={600} height={240}>
<AisTargetSymbol
{...SHARED}
x={70}
y={120}
name="Kerlaouen"
state="sleeping"
cog={150}
sog={3}
cpa={2}
tcpa={30}
/>
<AisTargetSymbol {...SHARED} x={210} y={120} name="Nordic Vega" cog={260} sog={11} cpa={1.8} tcpa={14} />
<AisTargetSymbol {...SHARED} x={350} y={120} name="244456789" cog={null} sog={null} cpa={null} tcpa={null} />
<AisTargetSymbol {...SHARED} x={480} y={120} name="Galatea" cog={80} sog={5.5} cpa={0.2} tcpa={3} />
</NauticalChart>
</div>
);
}
Track
Each segment fades by the age of its older end, from full strength to a fifth at maxAgeSeconds (30 minutes by
default), and a segment whose newer end is older than that is not drawn.
Code
import { NauticalChart } from "@bosunkit/components/nautical-chart";
import { TrackSymbol, type TrackPoint } from "@bosunkit/components/nautical-chart-symbols";
import type { ReactElement } from "react";
export const description: string =
"An hour of track, a position every 5 minutes, fading over 30 minutes: the older half is not drawn.";
const TRACK: readonly TrackPoint[] = Array.from({ length: 13 }, (_: unknown, index: number): TrackPoint => ({
x: 40 + index * 44,
y: 180 - Math.round(60 * Math.sin(index / 3)),
ageSeconds: (12 - index) * 300,
}));
export default function Track(): ReactElement {
return (
<div style={{ width: "100%", maxWidth: "40em" }}>
<NauticalChart staticSvg={null} width={600} height={240}>
<TrackSymbol points={TRACK} maxAgeSeconds={1800} />
</NauticalChart>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| xrequired | number | – | Where the vessel is, across the chart, in chart units. |
| yrequired | number | – | Where the vessel is, down the chart, in chart units. |
| headingrequired | number | null | – | True heading in degrees, where the bow points; null when it is not known. |
| cogrequired | number | null | – | Course over ground in degrees true; null when it is not known. |
| sogrequired | number | null | – | Speed over ground in knots (kn); null when it is not known. |
| pixelsPerNauticalMilerequired | number | – | The chart's scale: chart units per nautical mile. |
| vectorMinutes | number | undefined | COURSE_VECTOR_MINUTES | How far ahead the course vector reaches, in minutes; 6 by default. |
| headingLineLength | number | undefined | 60 | The heading line's length from the vessel's position, in chart units; 60 by default. |
| testId | string | undefined | – | data-testid on the symbol; its parts are ${testId}--hull, --heading-line and --course. |
| ref | Ref<SVGGElement> | undefined | – | The symbol's <g> element. |
| Prop | Type | Default | Description |
|---|---|---|---|
| xrequired | number | – | Where the target is, across the chart, in chart units. |
| yrequired | number | – | Where the target is, down the chart, in chart units. |
| namerequired | string | – | The target's name, or its MMSI when it has reported no name, drawn beside it. |
| cogrequired | number | null | – | Course over ground in degrees true; null when it is not known, and the triangle is then drawn unrotated. |
| sogrequired | number | null | – | Speed over ground in knots (kn); null when it is not known. |
| cparequired | number | null | – | Closest point of approach in nautical miles (NM); null when it is not known. |
| tcparequired | number | null | – | Time to the closest point of approach in minutes; negative once passed; null when not known. |
| cpaLimitrequired | number | – | The CPA limit in nautical miles: within it, and within tcpaLimit, the target is dangerous. |
| tcpaLimitrequired | number | – | The TCPA limit in minutes. |
| state | AisTargetSymbolState | undefined | "activated" | activated (the default) draws the triangle with its course vector; sleeping a smaller one with none. |
| pixelsPerNauticalMilerequired | number | – | The chart's scale: chart units per nautical mile. |
| vectorMinutes | number | undefined | COURSE_VECTOR_MINUTES | How far ahead the course vector reaches, in minutes; 6 by default. |
| testId | string | undefined | – | data-testid on the symbol; its parts are ${testId}--triangle, --course and --label. |
| ref | Ref<SVGGElement> | undefined | – | The symbol's <g> element. |
| Prop | Type | Default | Description |
|---|---|---|---|
| pointsrequired | readonly TrackPoint[] | – | The positions, oldest first, in chart units. |
| maxAgeSeconds | number | undefined | 1800 | The age at which the track has faded out, in seconds; older positions are not drawn. 1800 (30 min) by default. |
| testId | string | undefined | – | data-testid on the track; each segment is ${testId}--segment. |
Related
- NauticalChart: the chart whose dynamic layer the symbols draw in.
- AIS target row: the same targets as a list, with the same danger rule.
- Conning: the symbols on the helm screen's chart.