@expofp/wayfinding-source 0.0.0-experimental.3b0db16 → 0.0.0-stage

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,56 +1,3 @@
1
- # @expofp/wayfinding-source
1
+ # Temporary Holding Version
2
2
 
3
- Turns plan data into the input of `@expofp/wayfinding-compiler`, and says what the data loses on the way.
4
-
5
- ```ts
6
- import { assembleWayfindingSource } from '@expofp/wayfinding-source';
7
- import { compileWayfinding } from '@expofp/wayfinding-compiler';
8
-
9
- const { source, problems } = assembleWayfindingSource(plan); // plan: RawWayfindingSource
10
- const { graph, report } = compileWayfinding(source);
11
- // show `problems` (the plan data) and then `report.problems` (the generation) in one list
12
- ```
13
-
14
- ## Input: `RawWayfindingSource`
15
-
16
- - `units` — `m` or `ft`; anything else throws, no other units are supported.
17
- - `walls` — wall polylines by floor, flat `[x0, y0, x1, y1, …]` in plan units.
18
- - `poi` — every place a route may go to, stands included: `name` (a stand's number), `layer`, `rect`
19
- before rotation, `rotate`, the POI `type` name (English), and `shape` — rings in plan units, sent only
20
- when the drawn shape describes the POI better than its rectangle.
21
- - `unitScale` — 10 for a drawing before fp-ver 5, which drew in tenths; 1 when absent. Anything but a
22
- finite number above zero throws, like an unsupported unit: both come from the host, not from the plan.
23
- - `anchors` — map anchors, to check the units against.
24
- - `transitions` — shafts, one stop per floor served, each naming its `kind`: `stairs`, `escalator` or
25
- `elevator`, the kinds the Designer defines.
26
-
27
- ## What it decides
28
-
29
- - **How a POI reads**, by its English type name. **Crossable** and **terminal** match a listed stem at
30
- the start of a word, so `Toilets` and `Staircase` count and `Upstairs` does not. **Crossable** —
31
- `entrance`, `exit`, `stair`, `elevator`, `lift` or `escalator`; a stair or escalator icon often stands
32
- in an aisle, and its footprint must not block it. **Terminal** — `toilet`, `restroom`, `coat`, `stair`,
33
- `elevator`, `lift` or `escalator`. **Entrance** — any type whose name contains `entrance`, as the viewer
34
- reads it: a way into the building, which routes reach through the wall it stands across or next to
35
- (the compiler's `entrance`). The POI's own name is never read. Type names in other languages are not
36
- recognized: such a POI reads as a plain stand.
37
- - **Footprint** — the rectangle, rotated; with a `shape`, the shape as `parts` and the rectangle as `dock`.
38
- - **Floors** — one per wall group name; groups naming no floor are the one unnamed floor. When the plan
39
- has POI and none of them stands on a floor the walls are drawn on, all walls and POI are read as one
40
- floor — unless a shaft joins two of those floors: then they stay apart, and the POI are left out as
41
- naming none of them.
42
- - **Shafts** — a shaft with no `kind` is left out. Only an elevator (`elevator`, `lift`) stays on
43
- accessible-only routes: stairs, escalators and any kind not known are kept off them.
44
- - **Order** — each floor's POI by name, so the same plan gives the same source whatever order it lists them.
45
- - **Scale** — plan units per meter from `units` and `unitScale`.
46
-
47
- ## What it reports
48
-
49
- `problems` — for the designer, grouped: POI left out (no rectangle, a coordinate that is not a number, a
50
- floor with no walls while other floors have them, or no floor of a plan whose floors shafts join), POI
51
- read on a floor other than the one they name (routes look for them on theirs), wall floors read as one
52
- because no POI stands on any of them (right for one floor drawn in several groups, wrong for separate
53
- floors — the data cannot tell), shafts with no kind or
54
- left with fewer than two floors, units the map anchors disagree with (by more than 2 %, the accuracy of
55
- placing an anchor by hand). `diagnostics` — every fact, for debug and tests. What the compiler's own
56
- validator checks is not repeated here.
3
+ This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
package/package.json CHANGED
@@ -1,34 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/wayfinding-source",
3
- "version": "0.0.0-experimental.3b0db16",
4
- "type": "module",
5
- "description": "ExpoFP SDK internal: assembles the wayfinding compiler input from plan data and reports what the data loses",
6
- "homepage": "https://developer.expofp.com/",
7
- "license": "MIT",
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "sideEffects": false,
12
- "main": "./dist/index.js",
13
- "module": "./dist/index.js",
14
- "types": "./dist/index.d.ts",
15
- "exports": {
16
- "./package.json": "./package.json",
17
- ".": {
18
- "@expofp/source": "./src/index.ts",
19
- "types": "./dist/index.d.ts",
20
- "import": "./dist/index.js",
21
- "default": "./dist/index.js"
22
- }
23
- },
24
- "dependencies": {
25
- "tslib": "^2.3.0",
26
- "@expofp/geometry": "0.0.0-experimental.3b0db16",
27
- "@expofp/wayfinding-compiler": "0.0.0-experimental.3b0db16"
28
- },
29
- "files": [
30
- "dist",
31
- "!**/*.tsbuildinfo",
32
- "!**/*.map"
33
- ]
3
+ "version": "0.0.0-stage",
4
+ "stub": true,
5
+ "description": "Temporary package placeholder for staged publishing"
34
6
  }
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { assembleWayfindingSource, type SourceAssembly } from './lib/assemble.js';
2
- export { isEntrance } from './lib/poi-kinds.js';
3
- export type { RawPoi, RawPoiRef, RawTransition, RawTransitionStop, RawWayfindingSource, SourceDiagnostic, SourceHighlight, SourceProblem, SourceProblemKind, } from './lib/types.js';
4
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- // Public API: plan data in; the compiler's input and a report on the data out.
2
- export { assembleWayfindingSource } from './lib/assemble.js';
3
- export { isEntrance } from './lib/poi-kinds.js';
@@ -1,25 +0,0 @@
1
- import type { WayfindingSource } from '@expofp/wayfinding-compiler';
2
- import type { RawWayfindingSource, SourceDiagnostic, SourceProblem } from './types.js';
3
- /** The compiler input, and what the plan data lost on the way in. */
4
- export interface SourceAssembly {
5
- /** The compiler input. Same plan → byte-identical source. */
6
- readonly source: WayfindingSource;
7
- /** What a designer is told: grouped, in words. */
8
- readonly problems: readonly SourceProblem[];
9
- /** Every fact of the assembly, one per object. */
10
- readonly diagnostics: readonly SourceDiagnostic[];
11
- }
12
- /**
13
- * Assemble the compiler's input from the plan as its author holds it: each POI becomes a destination, the
14
- * walls are grouped by floor, and shafts keep the stops that name a floor. Pure.
15
- *
16
- * Every loss is a diagnostic — a POI with no rectangle, with a coordinate that is not a number, or on a
17
- * floor with no walls, POI that name no floor of a plan whose floors shafts join, a shaft with no kind, a
18
- * floorless stop, a shaft left with one floor, units the anchors disagree with — and those that
19
- * cost routes are also problems for the designer.
20
- * @param raw The plan as its author holds it.
21
- * @returns The compiler input, the problems and the diagnostics.
22
- * @throws {Error} When the units are neither meters nor feet.
23
- */
24
- export declare function assembleWayfindingSource(raw: RawWayfindingSource): SourceAssembly;
25
- //# sourceMappingURL=assemble.d.ts.map
@@ -1,388 +0,0 @@
1
- import { Rect } from '@expofp/geometry';
2
- import { isAccessible, isCrossable, isEntrance, isTerminal } from './poi-kinds.js';
3
- import { anchoredUnitsPerMeter, SCALE_TOLERANCE, unitsPerMeter } from './scale.js';
4
- /** How many names a message spells out before it counts the rest. */
5
- const NAMES_SHOWN = 8;
6
- /**
7
- * Names as a sentence tail: the first few in full, the rest counted.
8
- * @param refs The POI.
9
- * @returns `A, B and 3 more`.
10
- */
11
- function names(refs) {
12
- const shown = refs
13
- .slice(0, NAMES_SHOWN)
14
- .map((ref) => (ref.name === '' ? 'unnamed POI' : ref.name));
15
- const rest = refs.length - shown.length;
16
- return rest > 0 ? `${shown.join(', ')} and ${rest} more` : shown.join(', ');
17
- }
18
- const floorName = (layer) => (layer === '' ? 'The floor' : `Floor "${layer}"`);
19
- /**
20
- * A POI's rectangle as a ring, rotated.
21
- * @param poi The POI.
22
- * @returns The four corners, or nothing when it has no rectangle.
23
- */
24
- function cornersOf(poi) {
25
- if (!poi.rect)
26
- return null;
27
- const { minX, minY, maxX, maxY } = poi.rect;
28
- return Rect.fromMinMax({ x: minX, y: minY }, { x: maxX, y: maxY }, poi.rotate ?? 0)
29
- .corners()
30
- .map((p) => [p.x, p.y]);
31
- }
32
- /**
33
- * Assemble the compiler's input from the plan as its author holds it: each POI becomes a destination, the
34
- * walls are grouped by floor, and shafts keep the stops that name a floor. Pure.
35
- *
36
- * Every loss is a diagnostic — a POI with no rectangle, with a coordinate that is not a number, or on a
37
- * floor with no walls, POI that name no floor of a plan whose floors shafts join, a shaft with no kind, a
38
- * floorless stop, a shaft left with one floor, units the anchors disagree with — and those that
39
- * cost routes are also problems for the designer.
40
- * @param raw The plan as its author holds it.
41
- * @returns The compiler input, the problems and the diagnostics.
42
- * @throws {Error} When the units are neither meters nor feet.
43
- */
44
- export function assembleWayfindingSource(raw) {
45
- const scale = unitsPerMeter(raw);
46
- const problems = [];
47
- const diagnostics = [];
48
- // The source keys a stored compile, so each floor's destinations are sorted by name, not input order.
49
- const byId = (a, b) => {
50
- if (a.id === b.id)
51
- return 0;
52
- return a.id < b.id ? -1 : 1;
53
- };
54
- const destByLayer = new Map();
55
- const refsByLayer = new Map();
56
- const allDestinations = [];
57
- const noRect = [];
58
- const notFinite = [];
59
- raw.poi.forEach((poi, index) => {
60
- const layer = poi.layer ?? '';
61
- const ref = { layer, index, name: poi.name };
62
- const rect = cornersOf(poi);
63
- if (!rect) {
64
- noRect.push(ref);
65
- diagnostics.push({
66
- code: 'poi-no-rect',
67
- severity: 'warning',
68
- message: 'the POI has no rectangle and is left out',
69
- layer,
70
- poi: ref,
71
- });
72
- return;
73
- }
74
- // The compiler joins the drawn pieces and the rect into one footprint. `dock` is the rect alone: convex,
75
- // as a containment test on the graph's ends requires, and where routes arrive.
76
- const shape = poi.shape?.length
77
- ? poi.shape.map((ring) => ring.map(([x, y]) => [x, y]))
78
- : [];
79
- // A rectangle of no area is the compiler's to report; a point that is not a number leaves the POI out.
80
- if (![rect, ...shape].every((ring) => ring.every(([x, y]) => Number.isFinite(x) && Number.isFinite(y)))) {
81
- notFinite.push(ref);
82
- diagnostics.push({
83
- code: 'poi-not-finite',
84
- severity: 'warning',
85
- message: 'the POI rectangle or shape has a coordinate that is not a number; it is left out',
86
- layer,
87
- poi: ref,
88
- });
89
- return;
90
- }
91
- const destination = {
92
- id: poi.name,
93
- polygon: rect,
94
- ...(shape.length ? { parts: shape, dock: rect } : {}),
95
- crossable: isCrossable(poi.type),
96
- terminal: isTerminal(poi.type),
97
- entrance: isEntrance(poi.type),
98
- };
99
- diagnostics.push({
100
- code: 'poi-kind',
101
- severity: 'info',
102
- message: 'how the POI reads to the compiler',
103
- layer,
104
- poi: ref,
105
- facts: {
106
- crossable: destination.crossable === true,
107
- terminal: destination.terminal === true,
108
- entrance: destination.entrance === true,
109
- shape: shape.length > 0,
110
- },
111
- });
112
- const onFloor = destByLayer.get(layer);
113
- if (onFloor) {
114
- onFloor.push(destination);
115
- refsByLayer.get(layer).push(ref);
116
- }
117
- else {
118
- destByLayer.set(layer, [destination]);
119
- refsByLayer.set(layer, [ref]);
120
- }
121
- allDestinations.push(destination);
122
- });
123
- if (noRect.length)
124
- problems.push({
125
- kind: 'poi-left-out',
126
- cause: 'no-rect',
127
- message: `${noRect.length} POI ${noRect.length === 1 ? 'has' : 'have'} no rectangle and ${noRect.length === 1 ? 'is' : 'are'} left out of the routes: ${names(noRect)}.`,
128
- poi: noRect,
129
- highlight: [],
130
- });
131
- if (notFinite.length)
132
- problems.push({
133
- kind: 'poi-left-out',
134
- cause: 'not-finite',
135
- message: `${notFinite.length} POI ${notFinite.length === 1 ? 'has a coordinate' : 'have coordinates'} that ${notFinite.length === 1 ? 'is' : 'are'} not a number and ${notFinite.length === 1 ? 'is' : 'are'} left out of the routes: ${names(notFinite)}.`,
136
- poi: notFinite,
137
- highlight: [],
138
- });
139
- // One layer per id: several wall groups may share a floor, and groups naming none are the unnamed floor.
140
- const wallsByLayer = new Map();
141
- for (const group of raw.walls) {
142
- const layer = group.layer ?? '';
143
- const runs = wallsByLayer.get(layer) ?? [];
144
- for (const run of group.polylines)
145
- runs.push([...run]);
146
- wallsByLayer.set(layer, runs);
147
- }
148
- const wallFloors = [...wallsByLayer.keys()];
149
- // No POI stands on any floor the walls are drawn on.
150
- const noneOnWalls = allDestinations.length > 0 && !wallFloors.some((layer) => destByLayer.has(layer));
151
- // A shaft between two of the wall floors makes them floors of their own, not one floor drawn in groups.
152
- const stacked = (raw.transitions ?? []).some((t) => new Set(t.points.flatMap((p) => p.layer !== undefined && wallsByLayer.has(p.layer) ? [p.layer] : [])).size > 1);
153
- // Otherwise walls and POI name one floor differently, and every wall group is read as that one floor.
154
- const oneFloor = noneOnWalls && !stacked;
155
- const layers = oneFloor
156
- ? [
157
- {
158
- layer: wallFloors[0] ?? '',
159
- walls: [...wallsByLayer.values()].reduce((all, runs) => all.concat(runs), []),
160
- destinations: [...allDestinations].sort(byId),
161
- },
162
- ]
163
- : [...wallsByLayer].map(([layer, walls]) => ({
164
- layer,
165
- walls,
166
- destinations: [...(destByLayer.get(layer) ?? [])].sort(byId),
167
- }));
168
- if (oneFloor && wallsByLayer.size > 0) {
169
- const floor = layers[0].layer;
170
- // Right when one floor is drawn in several groups (BG and FG); wrong when the groups are floors of
171
- // their own, whose walls then lie on top of one another. The data cannot tell the two apart.
172
- if (wallFloors.length > 1) {
173
- diagnostics.push({
174
- code: 'floors-merged',
175
- severity: 'warning',
176
- message: 'no POI stands on a floor the walls are drawn on and no transition joins those floors, so every wall group is read as one floor',
177
- layer: floor,
178
- facts: { floors: wallFloors },
179
- });
180
- problems.push({
181
- kind: 'floors-merged',
182
- layer: floor,
183
- cause: 'no-poi-on-wall-floors',
184
- message: `The walls are drawn on ${wallFloors.length} floors (${wallFloors
185
- .map((name) => `"${name}"`)
186
- .join(', ')}) and no POI stands on any of them, so they are read as one floor. If they are separate floors, put each POI on its floor or join the floors with a transition.`,
187
- poi: [],
188
- highlight: [],
189
- });
190
- }
191
- // A POI is looked for on the floor it names, so a POI naming another floor than
192
- // the merged one is never found, however well it is docked.
193
- const mismatched = [];
194
- for (const [layer, refs] of refsByLayer)
195
- if (layer !== '' && layer !== floor)
196
- mismatched.push(...refs);
197
- if (mismatched.length) {
198
- for (const ref of mismatched)
199
- diagnostics.push({
200
- code: 'poi-floor-mismatch',
201
- severity: 'warning',
202
- message: `the POI names floor "${ref.layer}" but stands on floor "${floor}"; it is looked for on floor "${ref.layer}"`,
203
- layer: floor,
204
- poi: ref,
205
- });
206
- problems.push({
207
- kind: 'poi-floor-mismatch',
208
- layer: floor,
209
- cause: 'named-floor-not-drawn',
210
- message: `${mismatched.length} POI ${mismatched.length === 1 ? 'names a floor' : 'name floors'} the walls are not drawn on (${[
211
- ...new Set(mismatched.map((ref) => `"${ref.layer}"`)),
212
- ].join(', ')}); ${mismatched.length === 1 ? 'it is' : 'they are'} read on floor "${floor}", where routes cannot find ${mismatched.length === 1 ? 'it' : 'them'}: ${names(mismatched)}.`,
213
- poi: mismatched,
214
- highlight: outlinesOf(raw, mismatched, floor),
215
- });
216
- }
217
- }
218
- // Floors joined by shafts, and no POI on any of them: the POI name no floor the plan draws.
219
- if (noneOnWalls && stacked) {
220
- const refs = [...refsByLayer.values()].flat();
221
- for (const ref of refs)
222
- diagnostics.push({
223
- code: 'poi-floor-not-drawn',
224
- severity: 'warning',
225
- message: 'the POI names no floor the walls are drawn on and is left out',
226
- layer: ref.layer,
227
- poi: ref,
228
- });
229
- problems.push({
230
- kind: 'poi-left-out',
231
- cause: 'floor-not-drawn',
232
- message: `${refs.length} POI name no floor the walls are drawn on (${wallFloors
233
- .map((floor) => `"${floor}"`)
234
- .join(', ')}), so ${refs.length === 1 ? 'it is' : 'they are'} left out of the routes: ${names(refs)}.`,
235
- poi: refs,
236
- highlight: [],
237
- });
238
- }
239
- else if (!oneFloor)
240
- for (const [layer, refs] of refsByLayer) {
241
- // A POI on a floor no walls are drawn on never reaches the compiler: there is no floor for it.
242
- if (wallsByLayer.has(layer))
243
- continue;
244
- for (const ref of refs)
245
- diagnostics.push({
246
- code: 'poi-floor-without-walls',
247
- severity: 'warning',
248
- message: 'the POI stands on a floor with no walls and is left out',
249
- layer,
250
- poi: ref,
251
- });
252
- problems.push({
253
- kind: 'poi-left-out',
254
- layer,
255
- cause: 'floor-without-walls',
256
- message: `${floorName(layer)} has no walls; its ${refs.length} POI ${refs.length === 1 ? 'is' : 'are'} left out of the routes: ${names(refs)}.`,
257
- poi: refs,
258
- highlight: outlinesOf(raw, refs, layer),
259
- });
260
- }
261
- const transitions = assembleTransitions(raw, problems, diagnostics);
262
- const measured = anchoredUnitsPerMeter(raw.anchors ?? []);
263
- if (measured !== null && Math.abs(measured / scale - 1) > SCALE_TOLERANCE) {
264
- const facts = { measured: +measured.toFixed(3), declared: +scale.toFixed(3) };
265
- diagnostics.push({
266
- code: 'scale-mismatch',
267
- severity: 'warning',
268
- message: "the map anchors measure a scale the plan's units do not declare",
269
- facts,
270
- });
271
- problems.push({
272
- kind: 'scale-mismatch',
273
- cause: 'anchors-disagree',
274
- message: `The map anchors measure ${facts.measured} plan units per meter, but the plan declares ${facts.declared}; check its units.`,
275
- poi: [],
276
- highlight: [],
277
- });
278
- }
279
- return {
280
- source: { layers, transitions, unitsPerMeter: scale },
281
- problems,
282
- diagnostics,
283
- };
284
- }
285
- /**
286
- * The rectangles of some POI on their floor, for a problem to point at.
287
- * @param raw The raw source the POI are listed in.
288
- * @param refs The POI.
289
- * @param layer The floor to draw them on.
290
- * @returns One bounds highlight per POI that has a rectangle.
291
- */
292
- function outlinesOf(raw, refs, layer) {
293
- const out = [];
294
- for (const ref of refs) {
295
- const ring = cornersOf(raw.poi[ref.index]);
296
- if (!ring)
297
- continue;
298
- const xs = ring.map(([x]) => x);
299
- const ys = ring.map(([, y]) => y);
300
- out.push({
301
- type: 'bounds',
302
- layer,
303
- box: [Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)],
304
- });
305
- }
306
- return out;
307
- }
308
- /**
309
- * The plan's shafts in the compiler's shape. A stop binds by position, not by `id`, which an export may
310
- * leave meaningless. A shaft names its kind, and its kind says whether it is accessible.
311
- * @param raw The raw source.
312
- * @param problems Where the designer's problems go.
313
- * @param diagnostics Where the facts go.
314
- * @returns One entry per shaft that names its kind, stops with a floor only.
315
- */
316
- function assembleTransitions(raw, problems, diagnostics) {
317
- const transitions = [];
318
- for (const transition of raw.transitions ?? []) {
319
- const name = transition.id ? `Transition "${transition.id}"` : 'A transition';
320
- const id = transition.id ?? '';
321
- const kind = transition.kind?.trim();
322
- // The kind prices the shaft and tells it from a walk: without one there is no shaft to build.
323
- if (!kind) {
324
- diagnostics.push({
325
- code: 'transition-kind-missing',
326
- severity: 'warning',
327
- message: 'the transition names no kind and is left out',
328
- transition: id,
329
- });
330
- problems.push({
331
- kind: 'floors-not-connected',
332
- cause: 'kind-missing',
333
- message: `${name} names no kind (stairs, escalator, elevator…) and is left out; the floors it joined stay unconnected.`,
334
- poi: [],
335
- highlight: [],
336
- });
337
- continue;
338
- }
339
- const points = transition.points.filter((p) => p.layer !== undefined);
340
- const floorless = transition.points.length - points.length;
341
- if (floorless)
342
- diagnostics.push({
343
- code: 'transition-stop-no-floor',
344
- severity: 'warning',
345
- message: `${floorless} stop(s) of the transition name no floor and are left out`,
346
- transition: id,
347
- facts: { stops: floorless },
348
- });
349
- // A shaft left with one floor is the one problem reported; the floorless stop is its reason.
350
- if (points.length < 2) {
351
- diagnostics.push({
352
- code: 'transition-dropped',
353
- severity: 'warning',
354
- message: 'the transition keeps fewer than two stops and is left out',
355
- transition: id,
356
- facts: { stops: points.length },
357
- });
358
- problems.push({
359
- kind: 'floors-not-connected',
360
- cause: 'transition-dropped',
361
- message: `${name} serves fewer than two floors and is left out; the floors it joined stay unconnected.`,
362
- poi: [],
363
- highlight: [],
364
- });
365
- continue;
366
- }
367
- if (floorless)
368
- problems.push({
369
- kind: 'floors-not-connected',
370
- cause: 'stop-no-floor',
371
- message: `${name}: ${floorless} ${floorless === 1 ? 'stop has' : 'stops have'} no floor and ${floorless === 1 ? 'is' : 'are'} left out.`,
372
- poi: [],
373
- highlight: [],
374
- });
375
- transitions.push({
376
- ...(transition.id != null ? { id: transition.id } : {}),
377
- kind,
378
- points: points.map((p) => ({
379
- ...(p.id != null ? { id: p.id } : {}),
380
- layer: p.layer,
381
- x: p.x,
382
- y: p.y,
383
- })),
384
- ...(!isAccessible(kind) && { accessible: false }),
385
- });
386
- }
387
- return transitions;
388
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * Whether a route ends at this POI type rather than going round it.
3
- * @param type The POI type's name.
4
- * @returns True when one connection is the whole approach.
5
- */
6
- export declare function isTerminal(type: string | undefined): boolean;
7
- /**
8
- * Whether a route may pass through this POI type.
9
- * @param type The POI type's name.
10
- * @returns True when the route may pass straight through it.
11
- */
12
- export declare function isCrossable(type: string | undefined): boolean;
13
- /**
14
- * Whether this POI type is a way into the building: its English name contains "entrance".
15
- * @param type The POI type's name.
16
- * @returns True when routes reach the POI through the wall it stands across or next to.
17
- */
18
- export declare function isEntrance(type: string | undefined): boolean;
19
- /**
20
- * Whether an accessible-only route may take a transition of this kind.
21
- * @param kind The transition's kind.
22
- * @returns True for an elevator; false for stairs, an escalator and any kind it does not know.
23
- */
24
- export declare function isAccessible(kind: string): boolean;
25
- //# sourceMappingURL=poi-kinds.d.ts.map
@@ -1,70 +0,0 @@
1
- /**
2
- * POI types a route passes through: their footprints stay walkable, so an entrance in a doorway is the way
3
- * in, and a stair icon standing in an aisle does not block it. Matched as the start of a word of the
4
- * English type name, since type ids differ per plan.
5
- */
6
- const CROSSABLE_POI_TYPES = ['entrance', 'exit', 'stair', 'elevator', 'lift', 'escalator'];
7
- /**
8
- * POI types a visitor walks up to rather than around — a toilet, a cloakroom, a lift or stair door. One
9
- * connection serves them.
10
- */
11
- const TERMINAL_POI_TYPES = ['toilet', 'restroom', 'coat', 'stair', 'elevator', 'lift', 'escalator'];
12
- /**
13
- * The transition kinds a wheelchair can use, of the Designer's `stairs`, `escalator` and `elevator`.
14
- * A kind not named here is taken for stepped: an unknown one must not open an accessible route.
15
- */
16
- const STEP_FREE_TRANSITION_KINDS = ['elevator', 'lift'];
17
- /**
18
- * The words of a name, lowercased, so `Bag-Check` and `bag check` read alike. English only: a word in
19
- * another script holds no stem.
20
- * @param name The name.
21
- * @returns Its words.
22
- */
23
- function words(name) {
24
- return name
25
- .toLowerCase()
26
- .split(/[^a-z0-9]+/)
27
- .filter(Boolean);
28
- }
29
- /**
30
- * Whether a name holds one of the stems as a whole word or the start of one: `Toilets` and `Staircase`
31
- * do, `Upstairs` does not.
32
- * @param name The name.
33
- * @param stems The stems looked for.
34
- * @returns True when a word starts with a stem.
35
- */
36
- function namesOneOf(name, stems) {
37
- return words(name ?? '').some((word) => stems.some((stem) => word.startsWith(stem)));
38
- }
39
- /**
40
- * Whether a route ends at this POI type rather than going round it.
41
- * @param type The POI type's name.
42
- * @returns True when one connection is the whole approach.
43
- */
44
- export function isTerminal(type) {
45
- return namesOneOf(type, TERMINAL_POI_TYPES);
46
- }
47
- /**
48
- * Whether a route may pass through this POI type.
49
- * @param type The POI type's name.
50
- * @returns True when the route may pass straight through it.
51
- */
52
- export function isCrossable(type) {
53
- return namesOneOf(type, CROSSABLE_POI_TYPES);
54
- }
55
- /**
56
- * Whether this POI type is a way into the building: its English name contains "entrance".
57
- * @param type The POI type's name.
58
- * @returns True when routes reach the POI through the wall it stands across or next to.
59
- */
60
- export function isEntrance(type) {
61
- return /entrance/i.test(type ?? '');
62
- }
63
- /**
64
- * Whether an accessible-only route may take a transition of this kind.
65
- * @param kind The transition's kind.
66
- * @returns True for an elevator; false for stairs, an escalator and any kind it does not know.
67
- */
68
- export function isAccessible(kind) {
69
- return namesOneOf(kind, STEP_FREE_TRANSITION_KINDS);
70
- }
@@ -1,20 +0,0 @@
1
- import { type GeoAnchor } from '@expofp/geometry';
2
- import type { RawWayfindingSource } from './types.js';
3
- /**
4
- * How far a measured scale may sit from the declared one: placing an anchor by hand is worth a percent,
5
- * while a mistaken unit is off by 228% (feet for meters) or 900% (tenths).
6
- */
7
- export declare const SCALE_TOLERANCE = 0.02;
8
- /**
9
- * Plan units per meter, from the declared units.
10
- * @param raw The raw source.
11
- * @returns Plan units per meter.
12
- */
13
- export declare function unitsPerMeter(raw: RawWayfindingSource): number;
14
- /**
15
- * Plan units per meter measured from the map anchors, independent of the declared units.
16
- * @param anchors The anchors.
17
- * @returns The measured scale, or null with fewer than two usable anchors.
18
- */
19
- export declare function anchoredUnitsPerMeter(anchors: readonly GeoAnchor[]): number | null;
20
- //# sourceMappingURL=scale.d.ts.map
package/dist/lib/scale.js DELETED
@@ -1,42 +0,0 @@
1
- import { haversineDistance } from '@expofp/geometry';
2
- const FEET_PER_METER = 3.280839895;
3
- /**
4
- * How far a measured scale may sit from the declared one: placing an anchor by hand is worth a percent,
5
- * while a mistaken unit is off by 228% (feet for meters) or 900% (tenths).
6
- */
7
- export const SCALE_TOLERANCE = 0.02;
8
- /**
9
- * Plan units per meter, from the declared units.
10
- * @param raw The raw source.
11
- * @returns Plan units per meter.
12
- */
13
- export function unitsPerMeter(raw) {
14
- const units = String(raw.units).toLowerCase().trim();
15
- const scale = raw.unitScale ?? 1;
16
- // Both come from the host, not from the plan: a bad value is a caller's bug, not a designer's
17
- // problem, so it throws rather than being reported.
18
- if (units !== 'm' && units !== 'ft')
19
- throw new Error(`Unsupported plan units "${String(raw.units)}": only m and ft are supported.`);
20
- if (!(Number.isFinite(scale) && scale > 0))
21
- throw new Error(`Unsupported unitScale ${String(scale)}: a finite number above zero is required.`);
22
- return (units === 'm' ? 1 : FEET_PER_METER) * scale;
23
- }
24
- /**
25
- * Plan units per meter measured from the map anchors, independent of the declared units.
26
- * @param anchors The anchors.
27
- * @returns The measured scale, or null with fewer than two usable anchors.
28
- */
29
- export function anchoredUnitsPerMeter(anchors) {
30
- const usable = anchors.filter((a) => [a.local.x, a.local.y, a.geo.lat, a.geo.lng].every((value) => Number.isFinite(value)));
31
- const readings = [];
32
- for (let i = 0; i < usable.length; i++)
33
- for (let k = i + 1; k < usable.length; k++) {
34
- const meters = haversineDistance(usable[i].geo, usable[k].geo);
35
- const units = Math.hypot(usable[k].local.x - usable[i].local.x, usable[k].local.y - usable[i].local.y);
36
- if (meters > 0 && units > 0)
37
- readings.push(units / meters);
38
- }
39
- if (!readings.length)
40
- return null;
41
- return readings.reduce((sum, reading) => sum + reading, 0) / readings.length;
42
- }
@@ -1,130 +0,0 @@
1
- import type { GeoAnchor } from '@expofp/geometry';
2
- /** A POI as the plan holds it — a stand, a toilet, an entrance: every place a route may go to. */
3
- export interface RawPoi {
4
- /** The name the plan shows (a stand's number); it becomes the compiler's destination id. */
5
- readonly name: string;
6
- /** The floor it stands on; absent — the unnamed floor. */
7
- readonly layer?: string;
8
- /** Its rectangle before rotation, in plan units; absent — the POI is left out of the routes. */
9
- readonly rect?: {
10
- readonly minX: number;
11
- readonly minY: number;
12
- readonly maxX: number;
13
- readonly maxY: number;
14
- };
15
- /** Rotation of {@link rect} about its center, in radians, clockwise. */
16
- readonly rotate?: number;
17
- /**
18
- * The POI type's name as the plan writes it — "Toilets Male", "Entrances"; English only. Absent, or
19
- * in another language — a plain stand.
20
- */
21
- readonly type?: string;
22
- /**
23
- * The drawn shape as rings (triangles or polygons) in plan units, already rotated and placed; sent only
24
- * when it describes the POI better than {@link rect}. Absent — the rectangle.
25
- */
26
- readonly shape?: readonly (readonly (readonly [number, number])[])[];
27
- }
28
- /** One stop of a vertical connection — where a shaft opens onto a floor. */
29
- export interface RawTransitionStop {
30
- /** Where it stands, x, in plan units. */
31
- readonly x: number;
32
- /** Where it stands, y, in plan units. */
33
- readonly y: number;
34
- /** The authoring tool's own label for the stop. */
35
- readonly id?: string;
36
- /** The floor it opens onto; a stop without one is left out. */
37
- readonly layer?: string;
38
- }
39
- /** One vertical connection — a shaft, with a stop on every floor it serves. */
40
- export interface RawTransition {
41
- /** One stop per floor served. */
42
- readonly points: readonly RawTransitionStop[];
43
- /** The authoring tool's own id for the shaft. */
44
- readonly id?: string;
45
- /**
46
- * `stairs`, `escalator` or `elevator`, the kinds the Designer defines; absent — the transition is left
47
- * out. Only an elevator or a lift is taken by accessible-only routes.
48
- */
49
- readonly kind?: string;
50
- }
51
- /** A plan as its author holds it: floors of walls, POI with their rectangles, shafts, units. */
52
- export interface RawWayfindingSource {
53
- /** What the drawing measures in; any other value throws. */
54
- readonly units: 'm' | 'ft';
55
- /** Wall polylines by floor: flat `[x0, y0, x1, y1, …]` runs in plan units. */
56
- readonly walls: readonly {
57
- /** The group's polylines. */
58
- readonly polylines: readonly (readonly number[])[];
59
- /** The floor the group is drawn on; absent — the unnamed floor. */
60
- readonly layer?: string;
61
- }[];
62
- /** Every place a route may go to, stands included. */
63
- readonly poi: readonly RawPoi[];
64
- /**
65
- * Plan units per drawing unit — 10 for a drawing before fp-ver 5, which drew in tenths. Default 1;
66
- * anything but a finite number above zero throws.
67
- */
68
- readonly unitScale?: number;
69
- /**
70
- * Map anchors — a plan point (`local`, in plan units) and the place on Earth it sits at (`geo`); two or
71
- * more measure the scale, and the declared units are checked against it.
72
- */
73
- readonly anchors?: readonly GeoAnchor[];
74
- /** Vertical connections, one per shaft. */
75
- readonly transitions?: readonly RawTransition[];
76
- }
77
- /** A POI by its place in {@link RawWayfindingSource.poi}, with its name. */
78
- export interface RawPoiRef {
79
- /** The floor it stands on. */
80
- readonly layer: string;
81
- /** Position in `RawWayfindingSource.poi`. */
82
- readonly index: number;
83
- /** The name the plan gave it. */
84
- readonly name: string;
85
- }
86
- /** Where to look on the plan, in plan units. */
87
- export type SourceHighlight = {
88
- readonly type: 'bounds';
89
- readonly layer: string;
90
- readonly box: readonly [number, number, number, number];
91
- } | {
92
- readonly type: 'point';
93
- readonly layer: string;
94
- readonly at: readonly [number, number];
95
- };
96
- /** The kinds of plan-data problem a designer is shown. */
97
- export type SourceProblemKind = 'poi-left-out' | 'poi-floor-mismatch' | 'floors-merged' | 'floors-not-connected' | 'scale-mismatch';
98
- /** One problem with the plan data, shaped like the compiler's report `Problem`. */
99
- export interface SourceProblem {
100
- /** What kind of problem it is. */
101
- readonly kind: SourceProblemKind;
102
- /** The floor it belongs to; absent for whole-plan problems. */
103
- readonly layer?: string;
104
- /** Machine-readable cause within the kind. */
105
- readonly cause: string;
106
- /** Ready English text. */
107
- readonly message: string;
108
- /** The POI it touches. */
109
- readonly poi: readonly RawPoiRef[];
110
- /** Where to look, in plan units; empty when nothing exact is known. */
111
- readonly highlight: readonly SourceHighlight[];
112
- }
113
- /** One fact of the assembly. Plain data. */
114
- export interface SourceDiagnostic {
115
- /** Catalog code. */
116
- readonly code: string;
117
- /** `warning` — something is lost; `info` — how something was read. */
118
- readonly severity: 'warning' | 'info';
119
- /** One English sentence, technical. */
120
- readonly message: string;
121
- /** The floor it belongs to, when it belongs to one. */
122
- readonly layer?: string;
123
- /** The POI it is about, when there is one. */
124
- readonly poi?: RawPoiRef;
125
- /** The transition it is about, by its id. */
126
- readonly transition?: string;
127
- /** The assembly's own numbers and names. */
128
- readonly facts?: Readonly<Record<string, number | string | boolean | readonly string[]>>;
129
- }
130
- //# sourceMappingURL=types.d.ts.map
package/dist/lib/types.js DELETED
@@ -1 +0,0 @@
1
- export {};