Initial Watermaps import

This commit is contained in:
BuTzZ
2026-07-24 11:29:24 +02:00
commit 57f7b4dedb
129 changed files with 43136 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
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();
});
});