Skip to content
design
Components

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
Galatea ▲ Dangerous targetIroise ExpressKerlaouen
Own vessel with her heading line, course vector and track, a sailing vessel drawn dangerous inside the limits, a ferry passing clear and a fishing vessel asleep.
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.

01Heading and course are two linesThe hull and the solid heading line show where the bow points; the dashed vector shows where she is going over the ground. In a stream they part, and the chart shows both.
02No vector from noiseBelow 0.5 kn the course over ground is noise, so no vector is drawn. Without a heading the hull lies on the course; without either the position takes the two circles of IEC 62288's own-ship mark, which point nowhere.
03Danger in shape, word and colorA target inside both the CPA and the TCPA limit takes the larger IEC dangerous triangle, the alarm color and the words under its name.

Symbols

SymbolDrawsTurned byVector
OwnVesselSymbolThe hull and the heading line; two circles with neither heading nor courseHeading; course over ground without oneCourse over ground, six minutes' run, from 0.5 kn
AisTargetSymbolThe IEC target triangle and its nameCourse over ground; unturned triangle without oneCourse over ground, six minutes' run, from 0.5 kn; none asleep
TrackSymbolA line through past positionsNoneNone; 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

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.
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

KerlaouenNordic Vega244456789Galatea ▲ Dangerous target
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.
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.

An hour of track, a position every 5 minutes, fading over 30 minutes: the older half 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

PropTypeDefaultDescription
xrequirednumber–Where the vessel is, across the chart, in chart units.
yrequirednumber–Where the vessel is, down the chart, in chart units.
headingrequirednumber | null–True heading in degrees, where the bow points; null when it is not known.
cogrequirednumber | null–Course over ground in degrees true; null when it is not known.
sogrequirednumber | null–Speed over ground in knots (kn); null when it is not known.
pixelsPerNauticalMilerequirednumber–The chart's scale: chart units per nautical mile.
vectorMinutesnumber | undefinedCOURSE_VECTOR_MINUTESHow far ahead the course vector reaches, in minutes; 6 by default.
headingLineLengthnumber | undefined60The heading line's length from the vessel's position, in chart units; 60 by default.
testIdstring | undefined–data-testid on the symbol; its parts are ${testId}--hull, --heading-line and --course.
refRef<SVGGElement> | undefined–The symbol's <g> element.
PropTypeDefaultDescription
xrequirednumber–Where the target is, across the chart, in chart units.
yrequirednumber–Where the target is, down the chart, in chart units.
namerequiredstring–The target's name, or its MMSI when it has reported no name, drawn beside it.
cogrequirednumber | null–Course over ground in degrees true; null when it is not known, and the triangle is then drawn unrotated.
sogrequirednumber | null–Speed over ground in knots (kn); null when it is not known.
cparequirednumber | null–Closest point of approach in nautical miles (NM); null when it is not known.
tcparequirednumber | null–Time to the closest point of approach in minutes; negative once passed; null when not known.
cpaLimitrequirednumber–The CPA limit in nautical miles: within it, and within tcpaLimit, the target is dangerous.
tcpaLimitrequirednumber–The TCPA limit in minutes.
stateAisTargetSymbolState | undefined"activated"activated (the default) draws the triangle with its course vector; sleeping a smaller one with none.
pixelsPerNauticalMilerequirednumber–The chart's scale: chart units per nautical mile.
vectorMinutesnumber | undefinedCOURSE_VECTOR_MINUTESHow far ahead the course vector reaches, in minutes; 6 by default.
testIdstring | undefined–data-testid on the symbol; its parts are ${testId}--triangle, --course and --label.
refRef<SVGGElement> | undefined–The symbol's <g> element.
PropTypeDefaultDescription
pointsrequiredreadonly TrackPoint[]–The positions, oldest first, in chart units.
maxAgeSecondsnumber | undefined1800The age at which the track has faded out, in seconds; older positions are not drawn. 1800 (30 min) by default.
testIdstring | undefined–data-testid on the track; each segment is ${testId}--segment.
Do
Galatea ▲ Dangerous target
Mark a dangerous target by its symbol and words as well as its color.
Don't
Don't mark the danger by color alone. A red triangle among others reads as one more target to a tired eye or under the night palette.
  • 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.