lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests export pdfLageplanblatt.test.ts

4,6 KB Rohdatei
tests/export/pdfLageplanblatt.test.ts — 140 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf';
4 import { buildSignalPlan } from '@/domain/plan/signalPlan';
5 import { validateProject } from '@/domain/validation';
6 import { createStandardIntersectionProject } from '@/domain/model/factory';
7 import { toWinAnsi } from '@/render/pdfSurface';
8 import type { Lageplan } from '@/domain/geometrie/lageplan';
9 import type { Project } from '@/domain/model/project';
10
11 /**
12 * Das Lageplanblatt in der Unterlage.
13 *
14 * Die Zwischenzeiten dieser Anlage beruhen auf gezeichneten Fahrlinien, und
15 * von diesen Linien stand bis hierher keine einzige im Ausdruck. Der Abschnitt
16 * "Planungsgrundlage" wies die Unterlage nach, auf der gezeichnet wurde - die
17 * Zeichnung selbst fehlte.
18 */
19
20 const STICHTAG = new Date('2026-01-01T12:00:00Z');
21
22 function pdfText(bytes: Uint8Array): string {
23 const roh = new TextDecoder('latin1').decode(bytes);
24 const teile: string[] = [];
25 const suche = /stream\r?\n/g;
26 let treffer: RegExpExecArray | null;
27 while ((treffer = suche.exec(roh)) !== null) {
28 const start = treffer.index + treffer[0].length;
29 const ende = roh.indexOf('endstream', start);
30 if (ende < 0) continue;
31 let inhalt: string;
32 try {
33 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
34 } catch {
35 continue;
36 }
37 for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) {
38 teile.push(t[1]!.replace(/\\([()\\])/g, '$1'));
39 }
40 }
41 return teile.join(' ').replace(/\s+/g, ' ');
42 }
43
44 const LAGEPLAN: Lageplan = {
45 arbeitsbereiche: [],
46 signalgeber: [],
47 bild: {
48 datenUrl: 'data:image/png;base64,AA==',
49 breite: 400,
50 hoehe: 300,
51 herkunft: 'Luftbild Hessen',
52 geladenAm: '2026-01-01T10:00:00.000Z',
53 },
54 kalibrierung: {
55 von: { x: 0, y: 0 },
56 bis: { x: 100, y: 0 },
57 laengeMeter: 20,
58 herkunft: 'gemessen',
59 },
60 haltlinien: [
61 {
62 id: 'h1',
63 name: 'Nord',
64 art: 'zufahrt',
65 armId: null,
66 von: { x: 0, y: 0 },
67 bis: { x: 0, y: 100 },
68 },
69 ],
70 linien: [
71 {
72 id: 'l1',
73 name: 'K1',
74 mode: 'kfz',
75 movement: 'geradeaus',
76 breiteMeter: 3,
77 punkte: [
78 { x: 0, y: 50 },
79 { x: 200, y: 50 },
80 ],
81 haltlinieId: 'h1',
82 startT: 0.5,
83 signalGroupId: null,
84 },
85 ],
86 };
87
88 function projekt(mitLageplan: boolean): Project {
89 const roh = createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG);
90 return mitLageplan ? { ...roh, lageplan: LAGEPLAN } : roh;
91 }
92
93 function ausdruck(mitLageplan: boolean): string {
94 const p = projekt(mitLageplan);
95 const plan = buildSignalPlan(p);
96 return pdfText(
97 buildProjectPdf(p, plan, validateProject(p, plan, STICHTAG), DEFAULT_PDF_OPTIONS, STICHTAG),
98 );
99 }
100
101 describe('Planunterlage - Lageplan der Fahrlinien', () => {
102 it('druckt das Blatt, wo ein maßstäblicher Lageplan vorliegt', () => {
103 const text = ausdruck(true);
104 expect(text).toContain(toWinAnsi('Lageplan der Fahrlinien'));
105 // Der Maßstabsbalken gehört zum Blatt und wird mitgezeichnet.
106 expect(text).toMatch(/\d+ m/u);
107 });
108
109 it('nennt den Maßstabsbalken als maßgebende Angabe', () => {
110 /*
111 * DER WICHTIGSTE SATZ. Ein Blatt, das verkleinert kopiert wird, trägt ein
112 * falsches Verhältnis - und wer daraus einen Räumweg abgreift, bekommt
113 * eine zu kurze Zwischenzeit. Das ist die gefährliche Richtung.
114 */
115 const text = ausdruck(true);
116 expect(text).toContain(toWinAnsi('MASSGEBEND IST DER MASSSTABSBALKEN'));
117 expect(text).toContain(toWinAnsi('zu kurze Zwischenzeit'));
118 });
119
120 it('sagt, dass das Blatt kein Signallageplan ist', () => {
121 const text = ausdruck(true);
122 expect(text).toContain(toWinAnsi('KEIN Signallageplan'));
123 // Und die Bestandteileübersicht führt den bemaßten Signallageplan
124 // unverändert als nicht enthalten - das Blatt tritt nicht an seine Stelle.
125 expect(text).toContain(
126 toWinAnsi('Bemaßter Signallageplan mit Signalgeberstandorten NICHT Bestandteil'),
127 );
128 });
129
130 it('führt eine eigene Zeile in der Bestandteileübersicht', () => {
131 const mit = ausdruck(true);
132 expect(mit).toContain(toWinAnsi('Lageplan der Fahrlinien (maßstäblich, ohne Signalgeber)'));
133
134 // Und ohne auswertbaren Lageplan sagt dieselbe Zeile, dass es ihn nicht
135 // gibt - eine fehlende Angabe ist kein Nachweis, dass es sie nicht gibt.
136 const ohne = ausdruck(false);
137 expect(ohne).toContain(toWinAnsi('kein maßstäblich auswertbarer Lageplan im Projekt'));
138 expect(ohne).not.toContain(toWinAnsi('MASSGEBEND IST DER MASSSTABSBALKEN'));
139 });
140 });