149 lines
5.1 KiB
TypeScript
149 lines
5.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
canonicalizeMarinePois,
|
|
normalizedMarineFacilityName,
|
|
type MarinePoiCandidate
|
|
} from "../src/marine-poi-clustering.js";
|
|
|
|
const lock = (
|
|
id: string,
|
|
name: string | null,
|
|
lon: number,
|
|
properties: Record<string, unknown> = {},
|
|
overrides: Partial<MarinePoiCandidate> = {}
|
|
): MarinePoiCandidate => ({
|
|
id,
|
|
layer: "locks",
|
|
source: "osm",
|
|
sourceId: id,
|
|
name,
|
|
coordinate: { lon, lat: 51.695 },
|
|
properties,
|
|
...overrides
|
|
});
|
|
|
|
describe("marine POI canonicalization", () => {
|
|
it("normalizes facility type words but retains facility ordinals", () => {
|
|
expect(normalizedMarineFacilityName("Schleuse Werries")).toBe("werries");
|
|
expect(normalizedMarineFacilityName("Schleusengebiet Werries")).toBe("werries");
|
|
expect(normalizedMarineFacilityName("Werries Lock")).toBe("werries");
|
|
expect(normalizedMarineFacilityName("W.S.V. Sixhaven")).toBe("sixhaven");
|
|
expect(normalizedMarineFacilityName("Große Kammer Schleuse Ahsen")).toBe("ahsen");
|
|
expect(normalizedMarineFacilityName("Schleuse Ahsen Kammer 2")).toBe("ahsen");
|
|
expect(normalizedMarineFacilityName("Schleuse 55")).toBe("55");
|
|
expect(normalizedMarineFacilityName("Außentor")).toBeNull();
|
|
});
|
|
|
|
it("merges matching OSM and EuRIS locks and combines their contacts", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("w-osm", "Schleuse Werries", 7.867, {
|
|
website: "https://osm.example/werries",
|
|
phone: "+49 2381 9019290"
|
|
}),
|
|
lock(
|
|
"w-euris",
|
|
"Werries",
|
|
7.86708,
|
|
{ "ref:EU:RIS": "DEHMM00301LOCKS00404", phone: "+49 2381 9019-290", vhf: "22" },
|
|
{ source: "euris", sourceId: "DEHMM00301LOCKS00404" }
|
|
)
|
|
]);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]).toMatchObject({
|
|
entityId: "marine-poi:locks:euris:DEHMM00301LOCKS00404",
|
|
canonicalSource: "euris",
|
|
memberCount: 2,
|
|
properties: {
|
|
phone: "+49 2381 9019-290",
|
|
website: "https://osm.example/werries",
|
|
vhf: "22",
|
|
source: "EuRIS + OpenStreetMap"
|
|
}
|
|
});
|
|
});
|
|
|
|
it("does not merge neighbouring facilities with different specific names", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("a", "Wilhelminasluis", 4.724),
|
|
lock("b", "Hondsbossche Sluis", 4.72418)
|
|
]);
|
|
expect(result).toHaveLength(2);
|
|
});
|
|
|
|
it("merges duplicate facilities with the exact same name despite differing source websites", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("relation", "Kesselschleuse Emden", 7.2, { website: "https://city.example/lock" }),
|
|
lock("way", "Kesselschleuse Emden", 7.20008, { website: "https://operator.example/lock" })
|
|
]);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]?.properties.websites).toEqual([
|
|
"https://city.example/lock",
|
|
"https://operator.example/lock"
|
|
]);
|
|
});
|
|
|
|
it("does not merge equal names carrying conflicting official IDs", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("a", "Schleuse 55", 7.1, { "ref:EU:RIS": "DE-55" }),
|
|
lock("b", "Schleuse 55", 7.1001, { "ref:EU:RIS": "DE-56" })
|
|
]);
|
|
expect(result).toHaveLength(2);
|
|
});
|
|
|
|
it("assigns an unnamed gate to exactly the nearest anchor without bridging facilities", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("west", "Schleuse West", 7),
|
|
lock("east", "Schleuse Ost", 7.0018),
|
|
lock("gate", "Außentor", 7.0002, { waterway: "lock_gate" })
|
|
]);
|
|
expect(result).toHaveLength(2);
|
|
expect(result.find((poi) => poi.name === "Schleuse West")?.memberIds).toContain("gate");
|
|
expect(result.find((poi) => poi.name === "Schleuse Ost")?.memberIds).not.toContain("gate");
|
|
});
|
|
|
|
it("groups a named harbour anchor with its dock component", () => {
|
|
const result = canonicalizeMarinePois([
|
|
{
|
|
id: "marina",
|
|
layer: "harbours",
|
|
source: "osm",
|
|
sourceId: "w1",
|
|
name: "Stadthafen",
|
|
coordinate: { lon: 7.7, lat: 52 },
|
|
properties: { leisure: "marina" }
|
|
},
|
|
{
|
|
id: "dock",
|
|
layer: "harbours",
|
|
source: "osm",
|
|
sourceId: "w2",
|
|
name: "Stadthafen",
|
|
coordinate: { lon: 7.7004, lat: 52 },
|
|
properties: { waterway: "dock" }
|
|
}
|
|
]);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0]?.memberCount).toBe(2);
|
|
});
|
|
|
|
it("is independent of input order and adding a gate keeps the anchor ID stable", () => {
|
|
const anchor = lock("anchor", "Kesselschleuse Emden", 7.2, { lock: "yes" });
|
|
const gate = lock("gate", null, 7.2001, { waterway: "lock_gate" });
|
|
const forward = canonicalizeMarinePois([anchor, gate]);
|
|
const reverse = canonicalizeMarinePois([gate, anchor]);
|
|
const anchorOnly = canonicalizeMarinePois([anchor]);
|
|
expect(forward).toEqual(reverse);
|
|
expect(forward[0]?.entityId).toBe(anchorOnly[0]?.entityId);
|
|
});
|
|
|
|
it("keeps same-named facilities apart beyond the layer radius", () => {
|
|
const result = canonicalizeMarinePois([
|
|
lock("one", "Keersluis", 5),
|
|
lock("two", "Keersluis", 5.02)
|
|
]);
|
|
expect(result).toHaveLength(2);
|
|
});
|
|
});
|