Initial Watermaps import
This commit is contained in:
@@ -0,0 +1,605 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { RoutePlanner } from "../src/components/RoutePlanner";
|
||||
import type { RouteWeatherReport } from "../src/routeWeatherReport";
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
describe("RoutePlanner", () => {
|
||||
it("submits vessel profile when start and destination exist", () => {
|
||||
const onSubmit = vi.fn();
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 54.18, lon: 12.08 }}
|
||||
gpsPosition={{ lat: 54.18, lon: 12.08 }}
|
||||
destination={{ lat: 54.2, lon: 12.1 }}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={onSubmit}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Route berechnen" }));
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
start: { lat: 54.18, lon: 12.08 },
|
||||
destination: { lat: 54.2, lon: 12.1 },
|
||||
waypoints: [],
|
||||
departureTime: expect.any(String),
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, airDraftM: 2.5, beamM: 3.2, cruiseSpeedKn: 6 }
|
||||
});
|
||||
});
|
||||
|
||||
it("submits ordered waypoints together with the selected departure time", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const waypoints = [
|
||||
{ lat: 52.4, lon: 7.1 },
|
||||
{ lat: 51.9, lon: 7.45 }
|
||||
];
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 53.344167, lon: 7.186111 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 51.6814536, lon: 7.8042615 }}
|
||||
waypoints={waypoints}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={onSubmit}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onPickWaypoint={vi.fn()}
|
||||
onRemoveWaypoint={vi.fn()}
|
||||
onMoveWaypoint={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const localDeparture = "2026-07-20T08:30";
|
||||
|
||||
fireEvent.change(screen.getByLabelText("Abfahrt"), { target: { value: localDeparture } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Route berechnen" }));
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
start: { lat: 53.344167, lon: 7.186111 },
|
||||
destination: { lat: 51.6814536, lon: 7.8042615 },
|
||||
waypoints,
|
||||
departureTime: new Date(localDeparture).toISOString(),
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, airDraftM: 2.5, beamM: 3.2, cruiseSpeedKn: 6 }
|
||||
});
|
||||
});
|
||||
|
||||
it("offers an explicit start picking action", () => {
|
||||
const onPickStart = vi.fn();
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={null}
|
||||
gpsPosition={{ lat: 54.18, lon: 12.08 }}
|
||||
destination={null}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={onPickStart}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Start auf Karte setzen" }));
|
||||
|
||||
expect(onPickStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("offers an explicit destination picking action", () => {
|
||||
const onPickDestination = vi.fn();
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={null}
|
||||
gpsPosition={{ lat: 54.18, lon: 12.08 }}
|
||||
destination={null}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={onPickDestination}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Ziel auf Karte setzen" }));
|
||||
|
||||
expect(onPickDestination).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not offer Borkum or Hamm as direct destination shortcuts", () => {
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={null}
|
||||
gpsPosition={null}
|
||||
destination={null}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Borkum/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /Hamm/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("lets the skipper switch between returned route alternatives", () => {
|
||||
const onSelectRoute = vi.fn();
|
||||
const primary = routeOption("primary", "Hauptroute", 12);
|
||||
const alternative = routeOption("alternative", "Alternative 1", 14.5);
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 52, lon: 7 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 52, lon: 7.1 }}
|
||||
result={primary}
|
||||
routeOptions={[primary, alternative]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={onSelectRoute}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /Alternative 1/ }));
|
||||
|
||||
expect(onSelectRoute).toHaveBeenCalledWith("alternative");
|
||||
});
|
||||
|
||||
it("can collapse the route planner", () => {
|
||||
const onCollapse = vi.fn();
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={null}
|
||||
gpsPosition={null}
|
||||
destination={null}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={onCollapse}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Routenfenster ausblenden" }));
|
||||
|
||||
expect(onCollapse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("controls mobile sheet heights and leaves compact mode when the desktop layout starts", () => {
|
||||
let desktopLayout = false;
|
||||
let changeListener: (() => void) | null = null;
|
||||
const originalMatchMedia = window.matchMedia;
|
||||
const mediaQuery = {
|
||||
get matches() {
|
||||
return desktopLayout;
|
||||
},
|
||||
media: "(min-width: 720px)",
|
||||
onchange: null,
|
||||
addEventListener: vi.fn((_type: string, listener: () => void) => {
|
||||
changeListener = listener;
|
||||
}),
|
||||
removeEventListener: vi.fn(),
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
dispatchEvent: vi.fn()
|
||||
} as unknown as MediaQueryList;
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
configurable: true,
|
||||
value: vi.fn(() => mediaQuery)
|
||||
});
|
||||
|
||||
const { container, unmount } = render(
|
||||
<RoutePlanner
|
||||
startPoint={null}
|
||||
gpsPosition={null}
|
||||
destination={null}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const panel = container.querySelector("aside.route-panel");
|
||||
const body = container.querySelector(".route-panel-body");
|
||||
|
||||
expect(panel).toHaveAttribute("data-sheet-state", "half");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Routenfenster auf volle Höhe vergrößern" }));
|
||||
expect(panel).toHaveAttribute("data-sheet-state", "full");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Routenfenster auf kompakte Höhe verkleinern" }));
|
||||
expect(panel).toHaveAttribute("data-sheet-state", "compact");
|
||||
expect(body).toHaveAttribute("hidden");
|
||||
|
||||
desktopLayout = true;
|
||||
act(() => changeListener?.());
|
||||
expect(panel).toHaveAttribute("data-sheet-state", "half");
|
||||
expect(body).not.toHaveAttribute("hidden");
|
||||
|
||||
unmount();
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
configurable: true,
|
||||
value: originalMatchMedia
|
||||
});
|
||||
});
|
||||
|
||||
it("offers the course assistant only after a route was planned", () => {
|
||||
const onStartGuidance = vi.fn();
|
||||
const { rerender } = render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 52, lon: 7 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 52, lon: 7.1 }}
|
||||
result={null}
|
||||
routeOptions={[]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onStartGuidance={onStartGuidance}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByRole("button", { name: "Navigation starten" })).not.toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 52, lon: 7 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 52, lon: 7.1 }}
|
||||
result={routeOption("primary", "Hauptroute", 4)}
|
||||
routeOptions={[routeOption("primary", "Hauptroute", 4)]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onStartGuidance={onStartGuidance}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Navigation starten" }));
|
||||
expect(onStartGuidance).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("opens the compact result view and keeps critical warnings above secondary route details", () => {
|
||||
const result = {
|
||||
...routeOption("primary", "Hauptroute", 4),
|
||||
warnings: [
|
||||
{ code: "weather", severity: "caution" as const, message: "Wind aufmerksam beobachten." },
|
||||
{ code: "bridge", severity: "critical" as const, message: "Brücke ist zu niedrig." }
|
||||
]
|
||||
};
|
||||
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 52, lon: 7 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 52, lon: 7.1 }}
|
||||
result={result}
|
||||
routeOptions={[result]}
|
||||
weatherReport={null}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText("Routenergebnis")).toBeVisible();
|
||||
expect(screen.getByText("Brücke ist zu niedrig.")).toBeVisible();
|
||||
expect(screen.getByText("Wind aufmerksam beobachten.")).toBeVisible();
|
||||
expect(screen.queryByRole("button", { name: "Start auf Karte setzen" })).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Plan ändern" }));
|
||||
expect(screen.getByText("Route planen")).toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "Start auf Karte setzen" })).toBeVisible();
|
||||
expect(screen.getByRole("button", { name: "Zur Route" })).toBeVisible();
|
||||
});
|
||||
|
||||
it("shows the route weather report after a route was calculated", () => {
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 54.18, lon: 12.08 }}
|
||||
gpsPosition={{ lat: 54.18, lon: 12.08 }}
|
||||
destination={{ lat: 54.2, lon: 12.1 }}
|
||||
result={{
|
||||
geometry: { type: "LineString", coordinates: [[12.08, 54.18], [12.1, 54.2]] },
|
||||
distanceNm: 4.2,
|
||||
eta: null,
|
||||
warnings: [],
|
||||
minKnownDepthM: null,
|
||||
unknownDepthRatio: 1,
|
||||
dataSources: [],
|
||||
routingMode: "fairway"
|
||||
}}
|
||||
routeOptions={[]}
|
||||
weatherReport={weatherReportFixture}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("region", { name: "Fahrtbericht" })).toBeVisible();
|
||||
expect(screen.getByText(/Aufmerksam fahren/)).toBeVisible();
|
||||
expect(screen.getByText("18 kn 270°")).toBeVisible();
|
||||
expect(screen.getByText("1.2 m 290°")).toBeVisible();
|
||||
expect(screen.getByText(/Nicht passierbar/)).toBeVisible();
|
||||
expect(screen.getByText("Niedrige Brücke")).toBeVisible();
|
||||
});
|
||||
|
||||
it("keeps lock-delay planning and the current-adjusted ETA in the embedded route tool", () => {
|
||||
const adjustedReport: RouteWeatherReport = {
|
||||
...weatherReportFixture,
|
||||
adjustedEta: "2026-07-13T14:45:00.000Z",
|
||||
currentAdjustmentMinutes: 45,
|
||||
averageAlongRouteCurrentKn: -0.6
|
||||
};
|
||||
const result = routeOption("primary", "Hauptroute", 18);
|
||||
|
||||
render(
|
||||
<RoutePlanner
|
||||
startPoint={{ lat: 53.3, lon: 7.2 }}
|
||||
gpsPosition={null}
|
||||
destination={{ lat: 52.9, lon: 7.4 }}
|
||||
result={result}
|
||||
routeOptions={[result]}
|
||||
weatherReport={adjustedReport}
|
||||
weatherLoading={false}
|
||||
weatherError={null}
|
||||
routeLocks={[
|
||||
{
|
||||
id: "lock-1",
|
||||
name: "Testschleuse",
|
||||
coordinate: { lat: 53.1, lon: 7.3 },
|
||||
routeDistanceNm: 8,
|
||||
distanceFromRouteNm: 0.02,
|
||||
openingHours: "06:00-22:00",
|
||||
phone: "+49 123 456",
|
||||
vhf: "20",
|
||||
website: null
|
||||
}
|
||||
]}
|
||||
loading={false}
|
||||
error={null}
|
||||
pickMode={null}
|
||||
onSubmit={vi.fn()}
|
||||
onPickStart={vi.fn()}
|
||||
onPickDestination={vi.fn()}
|
||||
onClearStart={vi.fn()}
|
||||
onClearDestination={vi.fn()}
|
||||
onUseGpsAsStart={vi.fn()}
|
||||
onSelectRoute={vi.fn()}
|
||||
onCollapse={vi.fn()}
|
||||
operationalPanelsVisible={false}
|
||||
embedded
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Strömungs-ETA/)).toBeVisible();
|
||||
fireEvent.click(screen.getByText("Schleusenplanung · 1 auf der Route"));
|
||||
expect(screen.getByRole("spinbutton", { name: /Pauschale/ })).toHaveValue(20);
|
||||
expect(screen.getByText(/Plan-ETA inkl. Schleusenpuffer/)).toBeVisible();
|
||||
expect(screen.queryByRole("region", { name: "Fahrtbericht" })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
const weatherReportFixture: RouteWeatherReport = {
|
||||
samples: [
|
||||
{
|
||||
label: "Start",
|
||||
coordinate: { lat: 54.18, lon: 12.08 },
|
||||
forecast: {
|
||||
waveHeightM: 0.8,
|
||||
waveDirectionDeg: 280,
|
||||
wavePeriodS: 4,
|
||||
windSpeed: 12,
|
||||
windDirectionDeg: 260,
|
||||
weatherCode: 2,
|
||||
temperatureC: 18,
|
||||
source: "Test",
|
||||
updatedAt: "2026-07-13T12:00:00.000Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Ziel",
|
||||
coordinate: { lat: 54.2, lon: 12.1 },
|
||||
forecast: {
|
||||
waveHeightM: 1.2,
|
||||
waveDirectionDeg: 290,
|
||||
wavePeriodS: 5,
|
||||
windSpeed: 18,
|
||||
windDirectionDeg: 270,
|
||||
weatherCode: 3,
|
||||
temperatureC: 18,
|
||||
source: "Test",
|
||||
updatedAt: "2026-07-13T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
maxWaveHeightM: 1.2,
|
||||
maxWindSpeedKn: 18,
|
||||
maxWavePeriodS: 5,
|
||||
strongestWindDirectionDeg: 270,
|
||||
highestWaveDirectionDeg: 290,
|
||||
severity: "caution",
|
||||
summary: "Aufmerksam fahren: bis 18 kn Wind, 1.2 m Welle.",
|
||||
source: "Test",
|
||||
updatedAt: "2026-07-13T12:00:00.000Z",
|
||||
unavailableSamples: 0,
|
||||
departureTime: "2026-07-13T12:00:00.000Z",
|
||||
adjustedEta: null,
|
||||
currentAdjustmentMinutes: null,
|
||||
averageAlongRouteCurrentKn: null,
|
||||
bridgeReport: {
|
||||
bridges: [
|
||||
{
|
||||
id: "bridge-low",
|
||||
name: "Niedrige Brücke",
|
||||
label: "Niedrige Brücke H 2.2 m",
|
||||
coordinate: { lat: 54.19, lon: 12.09 },
|
||||
distanceNm: 0,
|
||||
clearanceM: 2.2,
|
||||
clearanceLabel: "H 2.2 m",
|
||||
requiredAirDraftM: 2.5,
|
||||
marginM: -0.3,
|
||||
status: "too_low",
|
||||
source: "OSM/Geofabrik"
|
||||
}
|
||||
],
|
||||
requiredAirDraftM: 2.5,
|
||||
checkedCount: 1,
|
||||
unknownCount: 0,
|
||||
tooLowCount: 1,
|
||||
tightCount: 0,
|
||||
minClearanceM: 2.2,
|
||||
severity: "critical",
|
||||
summary: "Nicht passierbar: 1 Brücke(n) niedriger als 2.5 m Bootshöhe.",
|
||||
source: "OSM/Geofabrik",
|
||||
updatedAt: "2026-07-13T12:00:00.000Z"
|
||||
}
|
||||
};
|
||||
|
||||
function routeOption(id: string, name: string, distanceNm: number) {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
geometry: { type: "LineString" as const, coordinates: [[7, 52], [7.1, 52]] as [number, number][] },
|
||||
distanceNm,
|
||||
eta: "2026-07-13T14:00:00.000Z",
|
||||
warnings: [],
|
||||
minKnownDepthM: null,
|
||||
unknownDepthRatio: 1,
|
||||
dataSources: [],
|
||||
routingMode: "fairway" as const
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user