import { describe, expect, it } from 'vitest'; import { massketten } from '@/domain/geometrie/masskette'; import { planblatt } from '@/domain/geometrie/planblatt'; import type { Lageplan } from '@/domain/geometrie/lageplan'; /* * STUFE 2 DES SIGNALLAGEPLAN-ENTWURFS: DAS BLATT WIRD NACHMESSBAR, OHNE EINE * ZWEITE RECHNUNG ZU BEKOMMEN. * * Das Lageplanblatt traegt seit 5.32.0 einen Massstabsbalken - wer eine Laenge * wissen will, greift sie ab. Die Masskette nennt die Zahl stattdessen, und * zwar DIESELBE, die das Programm kennt. * * DIE GEFAHR IST NICHT DIE FEHLENDE KETTE, SONDERN DIE FALSCHE. Eine Masszahl * auf dem Blatt sieht aus wie ein Ergebnis; wer sie liest, misst nicht nach. * Deshalb prueft diese Datei dreierlei: dass die Zahl aus der Vermessung * stammt und nicht aus der Blattgeometrie, dass sie sich mit der Blattgroesse * NICHT aendert, und dass die beiden Groessen, die das Programm bewusst * offenlaesst, auch keine Kette bekommen. */ /** 10 Bildpunkte je Meter - eine glatte Kalibrierung fuer nachrechenbare Zahlen. */ function lageplanMit(teile: Partial = {}): Lageplan { return { bild: { datenUrl: 'data:image/png;base64,AA', breite: 1000, hoehe: 1000, herkunft: 'pruefstueck.png', geladenAm: '2026-09-12T09:00:00.000Z', }, kalibrierung: { von: { x: 0, y: 0 }, bis: { x: 100, y: 0 }, laengeMeter: 10, herkunft: 'gemessen', }, haltlinien: [], linien: [], arbeitsbereiche: [], signalgeber: [], ...teile, }; } const HALTLINIE = { id: 'h1', name: 'H1', art: 'zufahrt' as const, armId: null, von: { x: 100, y: 100 }, // 60 Bildpunkte bei 10 Bildpunkten je Meter sind 6,0 m. bis: { x: 100, y: 160 }, }; const FAHRLINIE = { id: 'l1', name: 'A1', mode: 'kfz' as const, movement: 'geradeaus' as const, breiteMeter: 3.25, punkte: [ { x: 100, y: 130 }, { x: 400, y: 130 }, ], haltlinieId: 'h1', startT: 0.5, signalGroupId: null, }; const ARBEITSBEREICH = { id: 'b1', name: 'Arbeitsstelle', breiteMeter: 4, // 200 Bildpunkte sind 20,0 m. punkte: [ { x: 200, y: 300 }, { x: 400, y: 300 }, ], }; function ketten(lageplan: Lageplan, breite = 600, hoehe = 300) { const blatt = planblatt(lageplan, breite, hoehe); expect(blatt, 'kein Blatt').not.toBeNull(); return massketten(lageplan, blatt!); } describe('Massketten des Lageplanblatts', () => { it('bemasst die Haltlinie NICHT', () => { /* * DIE BERICHTIGUNG VON 5.42.1, aus dem Betrieb gemeldet. * * Die Kette stand hier zuerst, mit der Begruendung, die Laenge sei * eindeutig messbar. Eindeutig messbar heisst aber nicht gedeckt: * * - Eine Haltlinie quert die Fahrbahn; ihre Laenge liest sich als * FAHRBAHNBREITE. Die Breite, die wirklich rechnet, steht anderswo - * `SignalGroup.fahrstreifenbreite`, unterhalb von 3,00 m ueber den * Faktor fb im Zeitbedarfswert. Zwei Zahlen fuer dasselbe, verschieden * gebildet, und die gezeichnete sah nach Messung aus. * - An einer Arbeitsstelle ist die verbleibende Fahrbahnbreite ein * Anordnungskriterium. Wer "2,6 m" an einer Haltlinie liest, zieht * daraus einen Schluss, den dieses Programm nicht decken kann. * - Die Laenge kam im ganzen Programm sonst NICHT vor: keine Spalte, kein * Fenster, keine Ansage. Der Bearbeiter zeichnete nach Augenmass und * erfuhr die Zahl erst im Ausdruck. * * Sie steht seit 5.42.1 in der Haltlinientabelle und im Anlegefenster - * als Auskunft beim Zeichnen, nicht als Zusicherung in einer Unterlage. */ const gefunden = ketten(lageplanMit({ haltlinien: [HALTLINIE], linien: [FAHRLINIE] })); expect(gefunden, 'die Haltlinie hat wieder eine Kette').toEqual([]); }); it('schreibt Laenge und Breite des Arbeitsbereichs an', () => { const gefunden = ketten( lageplanMit({ haltlinien: [HALTLINIE], linien: [FAHRLINIE], arbeitsbereiche: [ARBEITSBEREICH], }), ); expect(gefunden.find((k) => k.art === 'bereich-laenge')?.meter).toBe(20); expect(gefunden.find((k) => k.art === 'bereich-breite')?.meter).toBe(4); }); it('nennt dieselbe Zahl, gleich wie gross das Blatt ist', () => { /* * DER KERN DER SACHE. Die Kette liegt in Blattkoordinaten, die Zahl kommt * aus den Bildpunkten. Waere sie aus der Blattgeometrie gebildet, stuende * auf einem kleineren Blatt eine kleinere Laenge - und die Zeichnung * widerspraeche der Tabelle daneben. */ const plan = lageplanMit({ haltlinien: [HALTLINIE], linien: [FAHRLINIE], arbeitsbereiche: [ARBEITSBEREICH], }); const klein = ketten(plan, 300, 150).find((k) => k.art === 'bereich-laenge'); const gross = ketten(plan, 900, 450).find((k) => k.art === 'bereich-laenge'); expect(klein!.meter).toBe(gross!.meter); // Die Zeichnung selbst faellt dagegen sehr wohl verschieden aus. expect(klein!.punkte[0]).not.toEqual(gross!.punkte[0]); }); it('bemasst weder den Abstand zweier Haltlinien noch den zum Arbeitsbereich', () => { /* * Beide Groessen gibt es, und beide bleiben ohne Kette: * * - Der Abstand zweier Haltlinien IST an der Engstelle die * Engstellenlaenge, und die ist in diesem Programm der groesste * erfasste Raeumweg. Zwei Wege zu derselben Zahl, und der Pruefer * glaubte der gezeichneten. * - Der Abstand zum Arbeitsbereich steht in der Tabelle, nennt aber * bewusst nicht, zu WELCHEM Ende er misst (arbeitsbereich.ts). Eine * Kette zeigte auf eines und entschiede damit, was die Auskunft * offenlaesst. */ const zweite = { ...HALTLINIE, id: 'h2', name: 'H2', von: { x: 500, y: 100 }, bis: { x: 500, y: 160 }, }; const gefunden = ketten( lageplanMit({ haltlinien: [HALTLINIE, zweite], linien: [FAHRLINIE], arbeitsbereiche: [ARBEITSBEREICH], }), ); const arten = gefunden.map((k) => k.art); expect(arten).not.toContain('haltlinien-abstand'); expect(arten).not.toContain('bereich-abstand'); // 40,0 m waere der Abstand der beiden Haltlinien - er darf nirgends stehen. expect(gefunden.map((k) => k.meter)).not.toContain(40); }); it('setzt die Beschriftung auf halbe Zuglaenge und nicht zwischen die Enden', () => { // Bei einem geknickten Band laege die Mitte zwischen Anfang und Ende // NEBEN dem Band - die Zahl stuende dann auf der Fahrbahn. const geknickt = { ...ARBEITSBEREICH, punkte: [ { x: 200, y: 300 }, { x: 300, y: 300 }, { x: 300, y: 400 }, ], }; const kette = ketten( lageplanMit({ haltlinien: [HALTLINIE], linien: [FAHRLINIE], arbeitsbereiche: [geknickt] }), ).find((k) => k.art === 'bereich-laenge'); expect(kette!.meter).toBe(20); const mittePunkt = kette!.beschriftung; const zwischenEnden = { x: (kette!.punkte[0]!.x + kette!.punkte[2]!.x) / 2, y: (kette!.punkte[0]!.y + kette!.punkte[2]!.y) / 2, }; expect( Math.hypot(mittePunkt.x - zwischenEnden.x, mittePunkt.y - zwischenEnden.y), ).toBeGreaterThan(1); }); it('liefert ohne Kalibrierung keine einzige Kette', () => { // Ohne Massstab gibt es keine Meter - und eine Kette ohne Zahl waere ein // Strich, der so aussieht, als stuende etwas dran. const ohne = lageplanMit({ haltlinien: [HALTLINIE], linien: [FAHRLINIE] }); const blatt = planblatt(ohne, 600, 300); expect(massketten({ ...ohne, kalibrierung: null }, blatt!)).toEqual([]); }); });