266 lines
7.0 KiB
TypeScript
266 lines
7.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildRoute } from "@watermaps/shared";
|
|
import {
|
|
fairwayRowsToGraph,
|
|
mergeConnectedFairwayGraphs,
|
|
overpassToGraph
|
|
} from "../src/services/fairways.js";
|
|
|
|
describe("fairway graph extraction", () => {
|
|
it("builds a routable graph from PostGIS fairway rows", () => {
|
|
const graph = fairwayRowsToGraph(
|
|
[
|
|
{
|
|
id: "1",
|
|
source: "osm",
|
|
source_id: "way-1",
|
|
name: "Harbour Reach",
|
|
min_depth_m: "4.2",
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[10, 54],
|
|
[10.04, 54.02]
|
|
]
|
|
}
|
|
},
|
|
{
|
|
id: "2",
|
|
source: "osm",
|
|
source_id: "way-2",
|
|
name: "Outer Reach",
|
|
min_depth_m: 4.2,
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[10.04, 54.02],
|
|
[10.1, 54.04]
|
|
]
|
|
}
|
|
}
|
|
],
|
|
[9.9, 53.9, 10.2, 54.1]
|
|
);
|
|
|
|
expect(graph).not.toBeNull();
|
|
const route = buildRoute(
|
|
{
|
|
start: { lat: 54, lon: 10 },
|
|
destination: { lat: 54.04, lon: 10.1 },
|
|
vesselProfile: { draughtM: 1.2, safetyReserveM: 0.4 }
|
|
},
|
|
graph ?? undefined
|
|
);
|
|
|
|
expect(route?.routingMode).toBe("fairway");
|
|
expect(route?.dataSources).toContain("fairway-graph:postgis-9.900-53.900-10.200-54.100");
|
|
expect(route?.dataSources).toContain("postgis-osm");
|
|
});
|
|
|
|
it("routes the iPhone Emden coordinates through intermediate fairway vertices", () => {
|
|
const start = { lat: 53.3306, lon: 7.1752 };
|
|
const destination = { lat: 53.6741, lon: 7.1474 };
|
|
const graph = fairwayRowsToGraph(
|
|
[
|
|
{
|
|
id: "emden-main-reach",
|
|
source: "osm",
|
|
source_id: "way-main",
|
|
name: "Ems Fahrwasser",
|
|
min_depth_m: null,
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[7.1751368, 53.3331995],
|
|
[7.16, 53.42],
|
|
[7.1474, 53.55],
|
|
[7.18, 53.61]
|
|
]
|
|
}
|
|
},
|
|
{
|
|
id: "busetief-branch",
|
|
source: "osm",
|
|
source_id: "way-branch",
|
|
name: "Busetief",
|
|
min_depth_m: null,
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[7.1474, 53.55],
|
|
[7.1414995, 53.6668156]
|
|
]
|
|
}
|
|
}
|
|
],
|
|
[6.9974, 53.1806, 7.3252, 53.8241]
|
|
);
|
|
|
|
expect(graph).not.toBeNull();
|
|
const route = buildRoute(
|
|
{
|
|
start,
|
|
destination,
|
|
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 12 }
|
|
},
|
|
graph ?? undefined
|
|
);
|
|
|
|
expect(route).not.toBeNull();
|
|
expect(route?.routingMode).toBe("fairway");
|
|
expect(route?.dataSources).toContain("postgis-osm");
|
|
expect(route?.geometry.coordinates[0]).toEqual([start.lon, start.lat]);
|
|
expect(route?.geometry.coordinates.at(-1)).toEqual([destination.lon, destination.lat]);
|
|
expect(route?.geometry.coordinates.some(([lon, lat]) => lon === 7.1474 && lat === 53.55)).toBe(true);
|
|
});
|
|
|
|
it("builds a graph from OSM/OpenSeaMap Overpass ways", () => {
|
|
const graph = overpassToGraph(
|
|
{
|
|
elements: [
|
|
{
|
|
type: "way",
|
|
id: 123,
|
|
tags: {
|
|
"seamark:type": "navigation_line",
|
|
"seamark:navigation_line:minimum_depth": "3.5"
|
|
},
|
|
geometry: [
|
|
{ lat: 54, lon: 10 },
|
|
{ lat: 54.02, lon: 10.05 }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
[9.9, 53.9, 10.1, 54.1]
|
|
);
|
|
|
|
expect(graph).not.toBeNull();
|
|
expect(graph?.edges[0]?.source).toBe("osm-overpass-seamarks");
|
|
expect(graph?.edges[0]?.minDepthM).toBe(3.5);
|
|
});
|
|
|
|
it("accepts navigable canals but rejects explicitly closed waterways", () => {
|
|
const graph = overpassToGraph(
|
|
{
|
|
elements: [
|
|
{
|
|
type: "way",
|
|
id: 201,
|
|
tags: { waterway: "canal", boat: "yes", name: "Datteln-Hamm-Kanal" },
|
|
geometry: [
|
|
{ lat: 51.65, lon: 7.35 },
|
|
{ lat: 51.66, lon: 7.4 }
|
|
]
|
|
},
|
|
{
|
|
type: "way",
|
|
id: 202,
|
|
tags: { waterway: "canal", boat: "no", name: "Gesperrter Kanal" },
|
|
geometry: [
|
|
{ lat: 51.66, lon: 7.4 },
|
|
{ lat: 51.67, lon: 7.45 }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
[7.3, 51.6, 7.5, 51.7]
|
|
);
|
|
|
|
expect(graph).not.toBeNull();
|
|
expect(graph?.edges).toHaveLength(1);
|
|
expect(graph?.edges[0]?.name).toBe("Datteln-Hamm-Kanal");
|
|
});
|
|
|
|
it("topologically joins graph fragments from PostGIS and live data", () => {
|
|
const first = fairwayRowsToGraph(
|
|
[
|
|
{
|
|
id: "north",
|
|
source: "osm",
|
|
source_id: "north",
|
|
name: "Dortmund-Ems-Kanal",
|
|
min_depth_m: null,
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[7.3, 52.1],
|
|
[7.35, 52]
|
|
]
|
|
}
|
|
}
|
|
],
|
|
[7.2, 51.8, 7.6, 52.2]
|
|
);
|
|
const second = overpassToGraph(
|
|
{
|
|
elements: [
|
|
{
|
|
type: "way",
|
|
id: 301,
|
|
tags: { waterway: "canal", boat: "yes", name: "Datteln-Hamm-Kanal" },
|
|
geometry: [
|
|
{ lat: 52, lon: 7.35 },
|
|
{ lat: 51.9, lon: 7.5 }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
[7.2, 51.8, 7.6, 52.2]
|
|
);
|
|
const graph = mergeConnectedFairwayGraphs([first!, second!]);
|
|
const route = buildRoute(
|
|
{
|
|
start: { lat: 52.1, lon: 7.3 },
|
|
destination: { lat: 51.9, lon: 7.5 },
|
|
vesselProfile: { draughtM: 1.2, safetyReserveM: 0.3 }
|
|
},
|
|
graph ?? undefined
|
|
);
|
|
|
|
expect(route).not.toBeNull();
|
|
expect(route?.dataSources).toContain("postgis-osm");
|
|
expect(route?.dataSources).toContain("osm-overpass-waterway-canal");
|
|
});
|
|
|
|
it("carries OSM vessel restrictions into the routing graph", () => {
|
|
const graph = fairwayRowsToGraph(
|
|
[
|
|
{
|
|
id: "restricted",
|
|
source: "osm",
|
|
source_id: "way-restricted",
|
|
name: "Niedrige Durchfahrt",
|
|
min_depth_m: "3.0",
|
|
properties: { maxheight: "2.4 m", maxwidth: "3.2", maxdraft: "1.8", oneway: "yes" },
|
|
geometry: {
|
|
type: "LineString",
|
|
coordinates: [
|
|
[7, 52],
|
|
[7.04, 52]
|
|
]
|
|
}
|
|
}
|
|
],
|
|
[6.9, 51.9, 7.1, 52.1]
|
|
);
|
|
|
|
expect(graph?.edges[0]).toMatchObject({
|
|
maxAirDraftM: 2.4,
|
|
maxBeamM: 3.2,
|
|
maxDraughtM: 1.8,
|
|
oneway: true
|
|
});
|
|
expect(
|
|
buildRoute(
|
|
{
|
|
start: { lat: 52, lon: 7 },
|
|
destination: { lat: 52, lon: 7.04 },
|
|
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.3, airDraftM: 2.5, beamM: 3 }
|
|
},
|
|
graph ?? undefined
|
|
)
|
|
).toBeNull();
|
|
});
|
|
});
|