feat: add Docker/OpenTofu deployment and DE/NL routing
This commit is contained in:
@@ -184,6 +184,31 @@ describe("Watermaps API", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("reports unavailable fairway sources instead of claiming that no route exists", async () => {
|
||||
const app = await buildServer({
|
||||
cache: createCache(),
|
||||
fairwayService: {
|
||||
async getGraphsForRoute() {
|
||||
throw new AggregateError([new Error("local data missing"), new Error("Overpass timeout")]);
|
||||
},
|
||||
async close() {}
|
||||
}
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/routes",
|
||||
payload: {
|
||||
start: { lat: 54.1749, lon: 12.0731 },
|
||||
destination: { lat: 54.1833, lon: 12.0928 },
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 6 }
|
||||
}
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(503);
|
||||
expect(response.json().error).toBe("fairway_sources_unavailable");
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("returns a fairway route from Emden Außenhafen to Borkum Reede", async () => {
|
||||
const app = await buildServer({ cache: createCache() });
|
||||
const response = await app.inject({
|
||||
@@ -205,6 +230,103 @@ describe("Watermaps API", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("plans the Norddeich–Norderney route for the reported coordinates", async () => {
|
||||
const app = await buildServer({
|
||||
cache: createCache(),
|
||||
fairwayService: {
|
||||
async getGraphsForRoute() {
|
||||
return [
|
||||
{
|
||||
id: "norddeich-norderney-test",
|
||||
name: "Norddeich–Norderney",
|
||||
maxSnapDistanceNm: 0.5,
|
||||
nodes: [
|
||||
{ id: "norddeich", coordinate: { lat: 53.6234, lon: 7.1559 } },
|
||||
{ id: "fairway", coordinate: { lat: 53.66, lon: 7.16 } },
|
||||
{ id: "norderney", coordinate: { lat: 53.7023, lon: 7.1658 } }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: "norddeich-approach",
|
||||
name: "Norddeich Fahrwasser",
|
||||
from: "norddeich",
|
||||
to: "fairway",
|
||||
minDepthM: null,
|
||||
source: "local-geofabrik-test",
|
||||
coordinates: [
|
||||
{ lat: 53.6234, lon: 7.1559 },
|
||||
{ lat: 53.66, lon: 7.16 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "norderney-approach",
|
||||
name: "Norderney Fahrwasser",
|
||||
from: "fairway",
|
||||
to: "norderney",
|
||||
minDepthM: null,
|
||||
source: "local-geofabrik-test",
|
||||
coordinates: [
|
||||
{ lat: 53.66, lon: 7.16 },
|
||||
{ lat: 53.7023, lon: 7.1658 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
},
|
||||
async close() {}
|
||||
}
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/routes",
|
||||
payload: {
|
||||
start: { lat: 53.6234, lon: 7.1559 },
|
||||
destination: { lat: 53.7023, lon: 7.1658 },
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 6 }
|
||||
}
|
||||
});
|
||||
const body = response.json();
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(body.routingMode).toBe("fairway");
|
||||
expect(body.geometry.coordinates[0]).toEqual([7.1559, 53.6234]);
|
||||
expect(body.geometry.coordinates.at(-1)).toEqual([7.1658, 53.7023]);
|
||||
expect(body.distanceNm).toBeGreaterThan(4);
|
||||
expect(body.distanceNm).toBeLessThan(6);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("routes from Emden into the eastern lower Ems when all dynamic sources fail", async () => {
|
||||
const app = await buildServer({
|
||||
cache: createCache(),
|
||||
fairwayService: {
|
||||
async getGraphsForRoute() {
|
||||
throw new AggregateError([new Error("PostGIS unavailable"), new Error("Overpass timeout")]);
|
||||
},
|
||||
async close() {}
|
||||
}
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/routes",
|
||||
payload: {
|
||||
start: { lat: 53.3422, lon: 7.1871 },
|
||||
destination: { lat: 53.465, lon: 7.4734 },
|
||||
vesselProfile: { draughtM: 1.4, safetyReserveM: 0.5, cruiseSpeedKn: 6 }
|
||||
}
|
||||
});
|
||||
const body = response.json();
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(body.routingMode).toBe("fairway");
|
||||
expect(body.dataSources).toContain("fairway-graph:emden-east-ems-seed");
|
||||
expect(body.geometry.coordinates[0]).toEqual([7.1871, 53.3422]);
|
||||
expect(body.geometry.coordinates.at(-1)?.[0]).toBeCloseTo(7.4734, 3);
|
||||
expect(body.geometry.coordinates.at(-1)?.[1]).toBeCloseTo(53.465, 3);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("returns the inland fallback route from Emden to Hamm", async () => {
|
||||
const app = await buildServer({ cache: createCache() });
|
||||
const response = await app.inject({
|
||||
@@ -290,6 +412,102 @@ describe("Watermaps API", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("uses a shared local component for the reported Emden-Delfzijl coordinates", async () => {
|
||||
const coordinate = (lat: number, lon: number) => ({ lat, lon });
|
||||
const app = await buildServer({
|
||||
cache: createCache(),
|
||||
fairwayService: {
|
||||
async getGraphsForRoute() {
|
||||
return [
|
||||
{
|
||||
id: "local-geofabrik-component-snap",
|
||||
name: "Lokaler Geofabrik-Komponententest",
|
||||
maxSnapDistanceNm: 0.3,
|
||||
nodes: [
|
||||
{ id: "start-decoy-a", coordinate: coordinate(53.3416, 7.186) },
|
||||
{ id: "start-decoy-b", coordinate: coordinate(53.342, 7.187) },
|
||||
{ id: "destination-decoy-a", coordinate: coordinate(53.3282, 6.9304) },
|
||||
{ id: "destination-decoy-b", coordinate: coordinate(53.3286, 6.9294) },
|
||||
{ id: "shared-start", coordinate: coordinate(53.3395697, 7.1848883) },
|
||||
{ id: "shared-east", coordinate: coordinate(53.3321722, 7.1329034) },
|
||||
{ id: "shared-south", coordinate: coordinate(53.313849, 7.0011017) },
|
||||
{ id: "shared-destination", coordinate: coordinate(53.3303531, 6.9334715) }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: "start-decoy",
|
||||
name: "Nähere getrennte Startkante",
|
||||
from: "start-decoy-a",
|
||||
to: "start-decoy-b",
|
||||
coordinates: [coordinate(53.3416, 7.186), coordinate(53.342, 7.187)],
|
||||
minDepthM: null,
|
||||
source: "closer-but-disconnected-start"
|
||||
},
|
||||
{
|
||||
id: "destination-decoy",
|
||||
name: "Nähere getrennte Zielkante",
|
||||
from: "destination-decoy-a",
|
||||
to: "destination-decoy-b",
|
||||
coordinates: [coordinate(53.3282, 6.9304), coordinate(53.3286, 6.9294)],
|
||||
minDepthM: null,
|
||||
source: "closer-but-disconnected-destination"
|
||||
},
|
||||
{
|
||||
id: "shared-east",
|
||||
name: "Gemeinsamer lokaler Korridor Ost",
|
||||
from: "shared-start",
|
||||
to: "shared-east",
|
||||
coordinates: [coordinate(53.3395697, 7.1848883), coordinate(53.3321722, 7.1329034)],
|
||||
minDepthM: null,
|
||||
source: "local-geofabrik-germany+netherlands"
|
||||
},
|
||||
{
|
||||
id: "shared-south",
|
||||
name: "Gemeinsamer lokaler Korridor Süd",
|
||||
from: "shared-east",
|
||||
to: "shared-south",
|
||||
coordinates: [coordinate(53.3321722, 7.1329034), coordinate(53.313849, 7.0011017)],
|
||||
minDepthM: null,
|
||||
source: "local-geofabrik-germany+netherlands"
|
||||
},
|
||||
{
|
||||
id: "shared-west",
|
||||
name: "Gemeinsamer lokaler Korridor West",
|
||||
from: "shared-south",
|
||||
to: "shared-destination",
|
||||
coordinates: [coordinate(53.313849, 7.0011017), coordinate(53.3303531, 6.9334715)],
|
||||
minDepthM: null,
|
||||
source: "local-geofabrik-germany+netherlands"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
},
|
||||
async close() {}
|
||||
}
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/routes",
|
||||
payload: {
|
||||
start: { lat: 53.3416, lon: 7.186 },
|
||||
destination: { lat: 53.3282, lon: 6.9304 },
|
||||
vesselProfile: { draughtM: 1, safetyReserveM: 0.3 }
|
||||
}
|
||||
});
|
||||
const body = response.json();
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(body.routingMode).toBe("fairway");
|
||||
expect(body.geometry.coordinates[0]).toEqual([7.186, 53.3416]);
|
||||
expect(body.geometry.coordinates.at(-1)).toEqual([6.9304, 53.3282]);
|
||||
expect(body.dataSources).toContain("local-geofabrik-germany+netherlands");
|
||||
expect(body.dataSources).not.toContain("fairway-graph:ems-borkum-seed");
|
||||
expect(body.dataSources).not.toContain("closer-but-disconnected-start");
|
||||
expect(body.dataSources).not.toContain("closer-but-disconnected-destination");
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("returns distinct route alternatives when the waterway graph contains them", async () => {
|
||||
const coordinate = (lat: number, lon: number) => ({ lat, lon });
|
||||
const edge = (id: string, from: string, to: string, coordinates: Array<{ lat: number; lon: number }>) => ({
|
||||
|
||||
Reference in New Issue
Block a user