@johnmorrisdotca/kyogijo-stadiums 0.40.2 → 0.44.0
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/CHANGELOG.md +72 -0
- package/NOTICE.md +22 -0
- package/README.md +1 -3
- package/dist/cityctx-entry.d.ts +3 -0
- package/dist/cityctx-entry.js +3 -0
- package/dist/cityctx.d.ts +19 -0
- package/dist/cityctx.data.d.ts +11 -0
- package/dist/cityctx.data.js +7139 -0
- package/dist/cityctx.js +34 -0
- package/dist/context.d.ts +175 -0
- package/dist/context.js +369 -0
- package/dist/crowd.d.ts +9 -0
- package/dist/district-entry.d.ts +16 -1
- package/dist/district-entry.js +9 -0
- package/dist/district.d.ts +99 -4
- package/dist/district.dom.d.ts +31 -0
- package/dist/district.dom.js +52 -0
- package/dist/district.js +103 -25
- package/dist/district.turn.js +89 -19
- package/dist/gates.d.ts +56 -0
- package/dist/gates.js +95 -0
- package/dist/halls.d.ts +2 -0
- package/dist/halls.js +1 -1
- package/dist/kodokan.d.ts +116 -0
- package/dist/kodokan.data.d.ts +61 -0
- package/dist/kodokan.data.js +20 -0
- package/dist/kodokan.js +92 -0
- package/dist/osm-entry.d.ts +2 -0
- package/dist/osm-entry.js +2 -0
- package/dist/osm.context.data.d.ts +10 -0
- package/dist/osm.context.data.js +1107 -0
- package/dist/outside.d.ts +43 -0
- package/dist/outside.dom.d.ts +44 -0
- package/dist/outside.dom.js +203 -0
- package/dist/outside.js +227 -0
- package/dist/outside.paint.d.ts +57 -0
- package/dist/outside.paint.js +88 -0
- package/dist/plateau-entry.d.ts +3 -0
- package/dist/plateau-entry.js +3 -0
- package/dist/plateau.credit.d.ts +5 -0
- package/dist/plateau.credit.js +7 -0
- package/dist/plateau.data.d.ts +7 -0
- package/dist/plateau.data.js +757 -0
- package/dist/stadiums.d.ts +14 -0
- package/dist/stadiums.data.js +2 -1
- package/dist/stadiums.js +2 -0
- package/dist/strings.d.ts +3 -1
- package/dist/strings.js +4 -4
- package/dist/turn.dom.d.ts +10 -0
- package/dist/turn.dom.js +21 -0
- package/dist/turn.kit.d.ts +12 -3
- package/dist/turn.kit.js +7 -0
- package/dist/turn.markup.js +15 -3
- package/dist/turn.model.d.ts +3 -0
- package/dist/turn.style.js +20 -3
- package/dist/venue.real.d.ts +22 -1
- package/dist/venue.real.js +76 -7
- package/dist/venues.d.ts +9 -1
- package/dist/venues.data.js +2 -1
- package/dist/venues.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/walk.d.ts +56 -0
- package/dist/walk.data.d.ts +4 -0
- package/dist/walk.data.js +1295 -0
- package/dist/walk.js +120 -0
- package/package.json +25 -1
package/dist/district.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type ContextPlan, type ContextArea } from "./context.ts";
|
|
1
2
|
import { type StadiumConfigId } from "./stadiums.ts";
|
|
3
|
+
import type { OutsideOptions } from "./outside.ts";
|
|
2
4
|
import type { Pt } from "./crowd.ts";
|
|
3
5
|
import type { Language, SurfaceOptions } from "./types.ts";
|
|
4
6
|
/** How a district is fitted: `neighbours` tight round venues that stand close together, `city` at the real distances between venues that are kilometres apart. */
|
|
@@ -27,6 +29,28 @@ export interface DistrictOptions {
|
|
|
27
29
|
style?: "none";
|
|
28
30
|
/** Draw every venue's stands in 2D (true, the default) or its building only. */
|
|
29
31
|
stands?: boolean;
|
|
32
|
+
/** Mark each venue's gates on the plan (`venueGates`: a door's bar on the building's outline and a dashed stub the way a queue runs). Off when left out. */
|
|
33
|
+
gates?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The crowd outside the venues (`outside.ts`): `{ level: 25 }` puts a quarter of the seats' worth of people on the sidewalks, plazas and grounds round them, in the mounted views (`mountDistrict`, `mountDistrictTurn`). Off when left out. Asking
|
|
36
|
+
* for it at all, even at level 0, makes room round the buildings for the crowd (`OUTSIDE_ROOM`), so that the plan does not move when the level does; only a district with a venue that has a walkable mask (`OUTSIDE_VENUES`) is changed.
|
|
37
|
+
*/
|
|
38
|
+
outside?: OutsideOptions;
|
|
39
|
+
/**
|
|
40
|
+
* The buildings round the venues, as areas of data a page passes in (`TOKYO_DOME_CITY` of `@johnmorrisdotca/kyogijo-stadiums/plateau`): low in detail, flat on the floor shaded by height and, for the biggest and the tallest, standing
|
|
41
|
+
* in the 3D view. The frame is widened to take in the streets round the venues. None (the default) draws the district as it always was, and carries no data. The credit of each area goes into the description.
|
|
42
|
+
*/
|
|
43
|
+
context?: readonly ContextArea[];
|
|
44
|
+
/**
|
|
45
|
+
* The building a raised venue stands in (the Kodokan's main dojo, on the 7th floor): `xray` (the default) draws it with its walls giving way to the eye as it looks in, `solid` draws it whole, `none` leaves it out, and the dojo floats on its floor
|
|
46
|
+
* alone. Only a venue that stands in a building (`DistrictMember.shell`) is changed.
|
|
47
|
+
*/
|
|
48
|
+
building?: "xray" | "solid" | "none";
|
|
49
|
+
/**
|
|
50
|
+
* The floor of the Kodokan's building to show, 0 (the basement) to 8: the 7th is the great dojo, the 8th its gallery, the 5th and 6th their dojos as the Kodokan's floor guide counts them, the others a plain floor plate; the chosen floor's plate is picked out
|
|
51
|
+
* and what is drawn on the other floors is left out. Missing: the great dojo as it stands, on its floor, with nothing picked out. Only a venue that is a building of floors (`kodokan`) is changed.
|
|
52
|
+
*/
|
|
53
|
+
floor?: number;
|
|
30
54
|
}
|
|
31
55
|
/** A venue of a district as the district needs it: what it is called, where it stands, how it lies, and how it is drawn. */
|
|
32
56
|
export interface DistrictMember {
|
|
@@ -51,6 +75,47 @@ export interface DistrictMember {
|
|
|
51
75
|
seats: number;
|
|
52
76
|
/** The options the venue is drawn from. */
|
|
53
77
|
options: SurfaceOptions;
|
|
78
|
+
/** How high the venue's floor stands above the ground, in feet: the Kodokan's main dojo is on the 7th floor of its building, drawn in the air on its real floor. Missing: on the ground. */
|
|
79
|
+
elevation?: number;
|
|
80
|
+
/** The building the venue stands in, when the district draws it (`building`): its outline is `ring`, and its height in feet and storeys are these. */
|
|
81
|
+
shell?: {
|
|
82
|
+
height: number;
|
|
83
|
+
storeys: number;
|
|
84
|
+
};
|
|
85
|
+
/** The credit of data this venue is made from that is not OpenStreetMap's (the Kodokan's building: Project PLATEAU). */
|
|
86
|
+
attribution?: {
|
|
87
|
+
en: string;
|
|
88
|
+
ja: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What is drawn inside the venue, when it is not one surface: the rooms of a floor of a building (the Kodokan's 5th and 6th floors have two or three dojos), each its own surface with its own place. Missing: the venue's one surface, `options`
|
|
92
|
+
* at `centre` turned `heading`. An empty list draws no surface at all (a floor of plain rooms).
|
|
93
|
+
*/
|
|
94
|
+
parts?: DistrictPart[];
|
|
95
|
+
/** The floor of the building the venue is drawn at, and the height of its level above the ground in feet: the building's plate at that level is drawn in 3D, picked out from the see-through walls. */
|
|
96
|
+
plate?: {
|
|
97
|
+
floor: number;
|
|
98
|
+
level: number;
|
|
99
|
+
};
|
|
100
|
+
/** Every floor of the building with the height of its level in feet, and how thick a floor is in metres: each is drawn as a slab with an edge, the chosen one (`plate`) picked out and the rest faint. */
|
|
101
|
+
slabs?: {
|
|
102
|
+
floor: number;
|
|
103
|
+
level: number;
|
|
104
|
+
}[];
|
|
105
|
+
slabThickness?: number;
|
|
106
|
+
}
|
|
107
|
+
/** One surface of a venue that has several (`DistrictMember.parts`): what it is drawn from, where it stands, how it lies and how high its floor is. */
|
|
108
|
+
export interface DistrictPart {
|
|
109
|
+
key: string;
|
|
110
|
+
options: SurfaceOptions;
|
|
111
|
+
/** The middle of the surface, \`[longitude, latitude]\`. */
|
|
112
|
+
centre: readonly [number, number];
|
|
113
|
+
/** The compass heading of the surface's x axis, 0 to under 180. */
|
|
114
|
+
heading: number;
|
|
115
|
+
/** How high its floor stands above the ground, in feet. */
|
|
116
|
+
elevation?: number;
|
|
117
|
+
/** Which part of the surface's room is drawn: `room` (the floor, the walls and what stands on it, not the gallery over it) or `gallery` (what stands above the room's walls: the seats, the rail, the roof). Everything when missing. */
|
|
118
|
+
show?: "room" | "gallery";
|
|
54
119
|
}
|
|
55
120
|
/** Every key a district can be asked for: the arenas of `VENUES` and the stadiums of `STADIUMS`. */
|
|
56
121
|
export declare const DISTRICT_IDS: readonly string[];
|
|
@@ -66,19 +131,41 @@ export declare const isDistrictId: (id: string) => boolean;
|
|
|
66
131
|
* // bc-place:soccer stadium 52497
|
|
67
132
|
* ```
|
|
68
133
|
*/
|
|
69
|
-
export declare function districtMember(input: DistrictVenueInput, language?: Language): DistrictMember;
|
|
134
|
+
export declare function districtMember(input: DistrictVenueInput, language?: Language, floor?: number): DistrictMember;
|
|
135
|
+
/** Turn a point of the map (east, down the page) by `degrees` clockwise on the page. */
|
|
136
|
+
export declare const turnBy: ([x, y]: Pt, degrees: number) => Pt;
|
|
70
137
|
/** A venue placed in the district: where its surface's middle is, how far its drawing is turned, and where its building stands, all in feet in the district's frame (x to the right, y down the page). */
|
|
138
|
+
export interface PlacedPart {
|
|
139
|
+
part: DistrictPart;
|
|
140
|
+
/** The middle of the surface, and the degrees its drawing is turned clockwise, as for the venue's own. */
|
|
141
|
+
at: Pt;
|
|
142
|
+
angle: number;
|
|
143
|
+
}
|
|
71
144
|
export interface DistrictPlaced {
|
|
72
145
|
member: DistrictMember;
|
|
73
146
|
/** The middle of the surface. */
|
|
74
147
|
at: Pt;
|
|
75
148
|
/** The degrees the venue's own drawing (its field along x) is turned clockwise to lie as it does in the world. */
|
|
76
149
|
angle: number;
|
|
150
|
+
/** The surfaces of the venue as the plan places them (one, unless the venue has `parts`). */
|
|
151
|
+
parts: PlacedPart[];
|
|
77
152
|
/** The building's outline. */
|
|
78
153
|
footprint: Pt[];
|
|
79
154
|
/** Its convex hull, which the depth order of the 3D view separates buildings by. */
|
|
80
155
|
hull: Pt[];
|
|
81
156
|
}
|
|
157
|
+
/** A gate placed in a district: where it is on its venue's outline in the plan's frame, in feet, and the way it faces on the page, in degrees clockwise from the top. */
|
|
158
|
+
export interface DistrictGate {
|
|
159
|
+
key: string;
|
|
160
|
+
venue: string;
|
|
161
|
+
name: {
|
|
162
|
+
en: string;
|
|
163
|
+
ja: string;
|
|
164
|
+
};
|
|
165
|
+
at: Pt;
|
|
166
|
+
angle: number;
|
|
167
|
+
basis: "documented" | "plausible";
|
|
168
|
+
}
|
|
82
169
|
/** What a district is: its venues placed, the box that fits them, and how the map lies. */
|
|
83
170
|
export interface DistrictPlan {
|
|
84
171
|
options: DistrictOptions;
|
|
@@ -88,6 +175,10 @@ export interface DistrictPlan {
|
|
|
88
175
|
/** The way north points on the page, degrees clockwise from the top. */
|
|
89
176
|
north: number;
|
|
90
177
|
members: DistrictPlaced[];
|
|
178
|
+
/** The buildings round the venues that the options' `context` brings, placed in the frame; `undefined` without it. */
|
|
179
|
+
context: ContextPlan | undefined;
|
|
180
|
+
/** The gates of the venues, where the options ask for them: each on its building's outline in the plan's frame (feet), and the way it faces on the page. Empty otherwise. */
|
|
181
|
+
gates: DistrictGate[];
|
|
91
182
|
/** The frame the plan is drawn in, in feet: the venues' buildings with the margin the mode gives them. */
|
|
92
183
|
view: {
|
|
93
184
|
x: number;
|
|
@@ -101,9 +192,13 @@ export interface DistrictPlan {
|
|
|
101
192
|
b: string;
|
|
102
193
|
metres: number;
|
|
103
194
|
}[];
|
|
195
|
+
/** The `[longitude, latitude]` the plan's metres are measured from: the middle of the venues' surfaces. */
|
|
196
|
+
origin: readonly [number, number];
|
|
104
197
|
}
|
|
105
198
|
/** The margin round the buildings, as a share of the longer side of the district: tight for neighbours, wide for a city's grid and names. */
|
|
106
199
|
export declare const DISTRICT_MARGIN: Readonly<Record<DistrictMode, number>>;
|
|
200
|
+
/** The ground kept round the buildings for the crowd outside, in metres, when a district asks for one. */
|
|
201
|
+
export declare const OUTSIDE_ROOM = 160;
|
|
107
202
|
/**
|
|
108
203
|
* The plan of a district: each venue's surface placed where it is on the ground (the distance and the direction between middles come from the coordinates of OpenStreetMap's outlines, to a metre or so) and turned so
|
|
109
204
|
* that its long side points where it does in the world, and the frame that fits them. A venue whose orientation is not known (an arena the building gives no axis for) is placed with its rink east-west, as it is drawn alone.
|
|
@@ -117,7 +212,7 @@ export declare const DISTRICT_MARGIN: Readonly<Record<DistrictMode, number>>;
|
|
|
117
212
|
*/
|
|
118
213
|
export declare function planDistrict(options: DistrictOptions): DistrictPlan;
|
|
119
214
|
/** The colours and the type of a district's drawing, light and dark. Defaults sit in `:where()`, so a page's own rule wins. */
|
|
120
|
-
export declare const KYOGIJO_DISTRICT_STYLE = "\n:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9)}\n@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85)}}\n:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85)}\n.ky-district .ky-dist-ground{fill:var(--ky-dist-ground)}\n.ky-district .ky-dist-grid{fill:none;stroke:var(--ky-dist-grid);stroke-width:1}\n.ky-district .ky-dist-ink{fill:none;stroke:var(--ky-dist-ink);stroke-linecap:round;stroke-linejoin:round}\n.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,\"Segoe UI\",\"Hiragino Sans\",\"Noto Sans JP\",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}";
|
|
215
|
+
export declare const KYOGIJO_DISTRICT_STYLE = "\n:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9);--ky-dist-people:#c2410c;--ky-dist-b0:#d1d4cb;--ky-dist-b1:#c3c7be;--ky-dist-b2:#b2b7ae;--ky-dist-b3:#9ea49b;--ky-dist-bline:rgba(40,46,42,.28);--ky-dist-road:#cbcfc4;--ky-dist-deck:#bdc2b7;--ky-dist-walk:#eef0e8;--ky-dist-rail:#8b9288;--ky-dist-gate:#1f5fa8;--ky-dist-water:#c7d5dc;--ky-dist-green:#d2dcc4;--ky-dist-wood:#c3d0b6;--ky-dist-tree:#b3c2a3;--ky-dist-floor:#2f6db0}\n@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}}\n:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}\n.ky-district .ky-dist-ground{fill:var(--ky-dist-ground)}\n.ky-district .ky-dist-grid{fill:none;stroke:var(--ky-dist-grid);stroke-width:1}\n.ky-district .ky-dist-block{stroke:var(--ky-dist-bline);stroke-width:.5;stroke-linejoin:round}\n.ky-district .ky-dist-b0{fill:var(--ky-dist-b0)}\n.ky-district .ky-dist-b1{fill:var(--ky-dist-b1)}\n.ky-district .ky-dist-b2{fill:var(--ky-dist-b2)}\n.ky-district .ky-dist-b3{fill:var(--ky-dist-b3)}\n.ky-district .ky-dist-street{stroke-linejoin:round;stroke-linecap:butt}\n.ky-district .ky-dist-street-0{stroke:var(--ky-dist-road)}\n.ky-district .ky-dist-street-1{stroke:var(--ky-dist-deck)}\n.ky-district .ky-dist-street-2{stroke:var(--ky-dist-rail);stroke-dasharray:6 3}\n.ky-district .ky-dist-street-3{stroke:var(--ky-dist-walk)}\n.ky-district .ky-dist-land{stroke:none}\n.ky-district .ky-dist-water{fill:var(--ky-dist-water)}\n.ky-district .ky-dist-green{fill:var(--ky-dist-green)}\n.ky-district .ky-dist-wood{fill:var(--ky-dist-wood)}\n.ky-district .ky-dist-tree{fill:none;stroke:var(--ky-dist-tree);stroke-linecap:round}\n.ky-district .ky-dist-gate rect{fill:var(--ky-dist-gate);stroke:var(--ky-dist-gate);stroke-width:2.4}\n.ky-district .ky-dist-gate-plausible rect{fill:var(--ky-dist-ground);fill-opacity:.85}\n.ky-district .ky-dist-gate-queue{fill:none;stroke:var(--ky-dist-gate);stroke-width:3;stroke-dasharray:9 7}\n.ky-district .ky-dist-ghost{fill:var(--ky-dist-b1);fill-opacity:.35}\n.ky-district .ky-dist-floor{stroke:var(--ky-dist-floor);stroke-width:2}\n.ky-district .ky-dist-ink{fill:none;stroke:var(--ky-dist-ink);stroke-linecap:round;stroke-linejoin:round}\n.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,\"Segoe UI\",\"Hiragino Sans\",\"Noto Sans JP\",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}";
|
|
121
216
|
/**
|
|
122
217
|
* A venue's drawing as a part of the district's: its root made a nested `svg` that keeps the class, the sport and the colours the drawing's stylesheet reads, its box laid in its own frame (so that the group's turn
|
|
123
218
|
* and shift put it in place), and its title, description, apron, arrow and stylesheet taken out (the stylesheet is given back in `styles`).
|
|
@@ -150,7 +245,7 @@ export declare function describeDistrict(options: DistrictOptions, plan?: Distri
|
|
|
150
245
|
/** How a venue is drawn for a district: its own options with the district's language, theme and ids, in 2D with its stands or as the floor of the 3D view (its building only). */
|
|
151
246
|
export declare function drawMember(member: DistrictMember, options: DistrictOptions, index: number, id: string, how?: {
|
|
152
247
|
floor?: boolean;
|
|
153
|
-
}): string;
|
|
248
|
+
}, partIndex?: number): string;
|
|
154
249
|
/**
|
|
155
250
|
* The plan of a district as one SVG: the ground, a grid and a scale, a north arrow, and each venue's own drawing (its stands, or its building only for `stands: false`) put where it stands and turned as it lies, with a name beside each
|
|
156
251
|
* where `labels` says. `drawing` is how each venue is drawn; the 3D view gives it the building-only floor.
|
|
@@ -162,6 +257,6 @@ export declare function drawMember(member: DistrictMember, options: DistrictOpti
|
|
|
162
257
|
* // true true
|
|
163
258
|
* ```
|
|
164
259
|
*/
|
|
165
|
-
export declare function drawDistrict(options: DistrictOptions, plan?: DistrictPlan, drawing?: (member: DistrictMember, index: number) => string): string;
|
|
260
|
+
export declare function drawDistrict(options: DistrictOptions, plan?: DistrictPlan, drawing?: (member: DistrictMember, index: number, part: number) => string): string;
|
|
166
261
|
/** The venues a district was asked for that the package has no key for: none when it can be drawn. */
|
|
167
262
|
export declare const unknownVenues: (venues: readonly DistrictVenueInput[]) => string[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type DistrictOptions } from "./district.ts";
|
|
2
|
+
import type { OutsideLevel } from "./outside.paint.ts";
|
|
3
|
+
/** What `mountDistrict` gives back. */
|
|
4
|
+
export interface KyogijoDistrict {
|
|
5
|
+
readonly mode: "2d";
|
|
6
|
+
/** Draw again with other options (venues, fit, theme, level): the container's contents are replaced. */
|
|
7
|
+
update(options: DistrictOptions): void;
|
|
8
|
+
/** Show another level of the crowd outside at once, without drawing the plan again. A district with no crowd does nothing. */
|
|
9
|
+
setOutside(level: OutsideLevel): void;
|
|
10
|
+
/** What the crowd's last paint cost, in milliseconds, how many people it drew and how, or `undefined` where there is no crowd or it has not been drawn. */
|
|
11
|
+
outsideCost(): {
|
|
12
|
+
ms: number;
|
|
13
|
+
people: number;
|
|
14
|
+
mode: string;
|
|
15
|
+
pixels: number;
|
|
16
|
+
} | undefined;
|
|
17
|
+
/** The plan's SVG, or `null` after `destroy`. */
|
|
18
|
+
svg(): SVGSVGElement | null;
|
|
19
|
+
/** Empty the container and stop listening. */
|
|
20
|
+
destroy(): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mount a district's plan in a container, with the crowd outside over it when `options.outside` asks for one.
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts no-run
|
|
26
|
+
* import { mountDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district";
|
|
27
|
+
* const plan = mountDistrict(document.querySelector("#city")!, { venues: ["bc-place", "rogers-arena"], outside: { level: 50 } });
|
|
28
|
+
* plan.setOutside(100);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function mountDistrict(host: HTMLElement, options: DistrictOptions): KyogijoDistrict;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// A district's 2D plan in a page, with the crowd outside over it: `mountDistrict` writes `drawDistrict`'s SVG into a container and, where the district asks for a crowd (`DistrictOptions.outside`) and has a mask to put it on,
|
|
2
|
+
// lays one canvas over the drawing (`outside.dom.ts`). The plan is still: nothing animates, and the crowd is painted when its level or the box's size changes. Without a crowd it is `drawDistrict` written into the container.
|
|
3
|
+
import { drawDistrict, planDistrict } from "./district.js";
|
|
4
|
+
import { hasOutside, outsideCrowd, outsideLevelOf } from "./outside.js";
|
|
5
|
+
import { attachOutsidePlan } from "./outside.dom.js";
|
|
6
|
+
/**
|
|
7
|
+
* Mount a district's plan in a container, with the crowd outside over it when `options.outside` asks for one.
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts no-run
|
|
10
|
+
* import { mountDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district";
|
|
11
|
+
* const plan = mountDistrict(document.querySelector("#city")!, { venues: ["bc-place", "rogers-arena"], outside: { level: 50 } });
|
|
12
|
+
* plan.setOutside(100);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function mountDistrict(host, options) {
|
|
16
|
+
let layer;
|
|
17
|
+
let alive = true;
|
|
18
|
+
const draw = (next) => {
|
|
19
|
+
layer?.destroy();
|
|
20
|
+
layer = undefined;
|
|
21
|
+
const plan = planDistrict(next);
|
|
22
|
+
host.innerHTML = `<div class="ky-district-view" style="position:relative">${drawDistrict(next, plan)}</div>`;
|
|
23
|
+
const wrap = host.firstElementChild;
|
|
24
|
+
const svg = wrap.querySelector("svg.ky-district");
|
|
25
|
+
if (next.outside !== undefined && svg !== null && hasOutside(plan))
|
|
26
|
+
layer = attachOutsidePlan(wrap, svg, { level: outsideLevelOf(next), crowd: () => outsideCrowd(plan), viewBox: plan.view });
|
|
27
|
+
};
|
|
28
|
+
draw(options);
|
|
29
|
+
return {
|
|
30
|
+
mode: "2d",
|
|
31
|
+
update(next) {
|
|
32
|
+
if (alive)
|
|
33
|
+
draw(next);
|
|
34
|
+
},
|
|
35
|
+
setOutside(level) {
|
|
36
|
+
if (alive)
|
|
37
|
+
layer?.setLevel(level);
|
|
38
|
+
},
|
|
39
|
+
outsideCost() {
|
|
40
|
+
const last = layer?.last;
|
|
41
|
+
return last === undefined ? undefined : { ms: last.ms, people: last.stats.people, mode: last.stats.mode, pixels: last.pixels };
|
|
42
|
+
},
|
|
43
|
+
svg: () => (alive ? host.querySelector("svg.ky-district") : null),
|
|
44
|
+
destroy() {
|
|
45
|
+
if (!alive)
|
|
46
|
+
return;
|
|
47
|
+
alive = false;
|
|
48
|
+
layer?.destroy();
|
|
49
|
+
host.replaceChildren();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
package/dist/district.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// apart, with a grid, a scale and a name beside each. Nothing is drawn but what the venues are drawn from: no street, no park and no mark of a team, a league or a tournament; the credit
|
|
5
5
|
// `VENUE_ATTRIBUTION` goes with it. Pure: options in, a plan, and markup, out; `district.turn.ts` raises it in 3D.
|
|
6
6
|
import { arenaById, arenaNameIn } from "./arenas.js";
|
|
7
|
+
import { dojo } from "./budo/dojo.api.js";
|
|
8
|
+
import { kodokanMember } from "./kodokan.js";
|
|
9
|
+
import { contextFloor, contextFrame, placeContext, toneOf } from "./context.js";
|
|
7
10
|
import { crowdStands, drawKyogijo } from "./draw.js";
|
|
8
11
|
import { FEET_PER_METRE } from "./geometry.js";
|
|
9
12
|
import { KYOGIJO_STRINGS } from "./strings.js";
|
|
@@ -11,6 +14,8 @@ import { esc, hash } from "./format.js";
|
|
|
11
14
|
import { STADIUM_IDS, stadiumById, stadiumConfigs, stadiumFrameHeading, stadiumKind, stadiumNames, stadiumSeats } from "./stadiums.js";
|
|
12
15
|
import { VENUE_ATTRIBUTION, VENUE_IDS, venueById } from "./venues.js";
|
|
13
16
|
import { hullOf, metresFrom, middleOf, smallestBox } from "./venues.geo.js";
|
|
17
|
+
import { venueGates } from "./gates.js";
|
|
18
|
+
import { walkIdFor } from "./walk.js";
|
|
14
19
|
export const DISTRICT_MODES = ["neighbours", "city"];
|
|
15
20
|
const venueMember = (venue, language) => {
|
|
16
21
|
const arena = arenaById(venue.id);
|
|
@@ -46,7 +51,7 @@ const stadiumMember = (stadium, config, language) => {
|
|
|
46
51
|
};
|
|
47
52
|
};
|
|
48
53
|
/** Every key a district can be asked for: the arenas of `VENUES` and the stadiums of `STADIUMS`. */
|
|
49
|
-
export const DISTRICT_IDS = Object.freeze([...VENUE_IDS, ...STADIUM_IDS]);
|
|
54
|
+
export const DISTRICT_IDS = Object.freeze([...VENUE_IDS, ...STADIUM_IDS, "kodokan"]);
|
|
50
55
|
/** Whether a key is a venue a district can place. */
|
|
51
56
|
export const isDistrictId = (id) => DISTRICT_IDS.includes(id);
|
|
52
57
|
/**
|
|
@@ -59,7 +64,7 @@ export const isDistrictId = (id) => DISTRICT_IDS.includes(id);
|
|
|
59
64
|
* // bc-place:soccer stadium 52497
|
|
60
65
|
* ```
|
|
61
66
|
*/
|
|
62
|
-
export function districtMember(input, language = "en") {
|
|
67
|
+
export function districtMember(input, language = "en", floor) {
|
|
63
68
|
const [id, named] = typeof input === "string" ? input.split(":") : [input.id, input.config];
|
|
64
69
|
const venue = venueById(id);
|
|
65
70
|
if (venue !== undefined) {
|
|
@@ -74,14 +79,22 @@ export function districtMember(input, language = "en") {
|
|
|
74
79
|
throw new Error(`${id} has no configuration ${String(named)}: ${stadiumConfigs(stadium).join(", ")}`);
|
|
75
80
|
return stadiumMember(stadium, config, language);
|
|
76
81
|
}
|
|
82
|
+
// The Kodokan: a hall of the budō, its main dojo in the air in its building (`kodokan.ts`).
|
|
83
|
+
if (id === "kodokan") {
|
|
84
|
+
if (named !== undefined)
|
|
85
|
+
throw new Error(`kodokan has one configuration, not ${named}`);
|
|
86
|
+
return kodokanMember(language, floor);
|
|
87
|
+
}
|
|
77
88
|
throw new Error(`no venue ${String(id)}: the venues are ${DISTRICT_IDS.join(", ")}`);
|
|
78
89
|
}
|
|
79
90
|
// ---- The plan. ----
|
|
80
91
|
const RAD = Math.PI / 180;
|
|
81
92
|
/** Turn a point of the map (east, down the page) by `degrees` clockwise on the page. */
|
|
82
|
-
const turnBy = ([x, y], degrees) => [x * Math.cos(degrees * RAD) - y * Math.sin(degrees * RAD), x * Math.sin(degrees * RAD) + y * Math.cos(degrees * RAD)];
|
|
93
|
+
export const turnBy = ([x, y], degrees) => [x * Math.cos(degrees * RAD) - y * Math.sin(degrees * RAD), x * Math.sin(degrees * RAD) + y * Math.cos(degrees * RAD)];
|
|
83
94
|
/** The margin round the buildings, as a share of the longer side of the district: tight for neighbours, wide for a city's grid and names. */
|
|
84
95
|
export const DISTRICT_MARGIN = { neighbours: 0.09, city: 0.12 };
|
|
96
|
+
/** The ground kept round the buildings for the crowd outside, in metres, when a district asks for one. */
|
|
97
|
+
export const OUTSIDE_ROOM = 160;
|
|
85
98
|
const EARTH = 6371008.8;
|
|
86
99
|
const haversine = (a, b) => {
|
|
87
100
|
const [p, q] = [a[1] * RAD, b[1] * RAD];
|
|
@@ -104,7 +117,7 @@ export function planDistrict(options) {
|
|
|
104
117
|
throw new Error("a district needs a venue");
|
|
105
118
|
const language = options.language === "ja" ? "ja" : "en";
|
|
106
119
|
const mode = options.mode === "city" ? "city" : "neighbours";
|
|
107
|
-
const members = options.venues.map((v) => districtMember(v, language));
|
|
120
|
+
const members = options.venues.map((v) => districtMember(v, language, options.floor));
|
|
108
121
|
const keys = new Set();
|
|
109
122
|
for (const m of members) {
|
|
110
123
|
if (keys.has(m.key))
|
|
@@ -126,25 +139,56 @@ export function planDistrict(options) {
|
|
|
126
139
|
}
|
|
127
140
|
const placed = members.map((member, i) => {
|
|
128
141
|
const footprint = raw[i].footprint.map((p) => turnBy(p, turn));
|
|
129
|
-
|
|
142
|
+
// The venue's surfaces: its own, or the parts it is drawn from, each placed by its own middle and heading.
|
|
143
|
+
const parts = (member.parts ?? [{ key: member.key, options: member.options, centre: member.centre, heading: member.heading, ...(member.elevation === undefined ? {} : { elevation: member.elevation }) }]).map((part) => ({ part, at: turnBy(ground([part.centre])[0], turn), angle: part.heading - 90 + turn }));
|
|
144
|
+
return { member, parts, at: turnBy(raw[i].at, turn), angle: member.heading - 90 + turn, footprint, hull: hullOf(footprint) };
|
|
130
145
|
});
|
|
131
146
|
const xs = placed.flatMap((p) => p.footprint.map((q) => q[0]));
|
|
132
147
|
const ys = placed.flatMap((p) => p.footprint.map((q) => q[1]));
|
|
133
148
|
const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)];
|
|
134
|
-
const
|
|
135
|
-
|
|
149
|
+
const context = options.context !== undefined && options.context.length > 0;
|
|
150
|
+
// A district that asks for a crowd outside (and has a mask to put it on) keeps room round its buildings for it, whatever the level, so that the plan stays where it is as the level changes.
|
|
151
|
+
const room = options.outside !== undefined && walkIdFor(members.map((m) => m.id)) !== undefined ? OUTSIDE_ROOM * FEET_PER_METRE : 0;
|
|
152
|
+
const margin = Math.max(Math.max(x1 - x0, y1 - y0) * DISTRICT_MARGIN[mode], room);
|
|
153
|
+
// With context the frame takes in the streets round the buildings (and any landmark near them) as well.
|
|
154
|
+
const round = { x0: x0 - margin, y0: y0 - margin, x1: x1 + margin, y1: y1 + margin };
|
|
155
|
+
const wide = context ? contextFrame(options.context, origin, turn, { x0, y0, x1, y1 }) : round;
|
|
156
|
+
const framed = { x0: Math.min(round.x0, wide.x0), y0: Math.min(round.y0, wide.y0), x1: Math.max(round.x1, wide.x1), y1: Math.max(round.y1, wide.y1) };
|
|
157
|
+
const view = { x: framed.x0, y: framed.y0, w: framed.x1 - framed.x0, h: framed.y1 - framed.y0 };
|
|
136
158
|
const between = [];
|
|
137
159
|
members.forEach((a, i) => members.slice(i + 1).forEach((b) => between.push({ a: a.key, b: b.key, metres: Math.round(haversine(a.centre, b.centre) * 10) / 10 })));
|
|
138
|
-
|
|
160
|
+
const around = context ? placeContext(options.context, origin, turn, view, placed.map((p) => ({ footprint: p.footprint, shell: p.member.shell !== undefined }))) : undefined;
|
|
161
|
+
const gates = options.gates !== true ? [] : placed.flatMap((p) => venueGates(p.member.id).map((g) => ({ key: g.key, venue: p.member.key, name: g.name, at: turnBy(ground([g.at])[0], turn), angle: ((g.bearing + turn) % 360 + 360) % 360, basis: g.basis })));
|
|
162
|
+
return { options, mode, turn, north: turn, members: placed, origin, context: around, gates, view, between };
|
|
139
163
|
}
|
|
140
164
|
// ---- The drawing. ----
|
|
141
165
|
/** The colours and the type of a district's drawing, light and dark. Defaults sit in `:where()`, so a page's own rule wins. */
|
|
142
166
|
export const KYOGIJO_DISTRICT_STYLE = `
|
|
143
|
-
:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9)}
|
|
144
|
-
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85)}}
|
|
145
|
-
:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85)}
|
|
167
|
+
:where(.ky-district){--ky-dist-ground:#e1e4da;--ky-dist-grid:rgba(40,46,42,.16);--ky-dist-ink:#1f2320;--ky-dist-halo:rgba(255,255,255,.9);--ky-dist-people:#c2410c;--ky-dist-b0:#d1d4cb;--ky-dist-b1:#c3c7be;--ky-dist-b2:#b2b7ae;--ky-dist-b3:#9ea49b;--ky-dist-bline:rgba(40,46,42,.28);--ky-dist-road:#cbcfc4;--ky-dist-deck:#bdc2b7;--ky-dist-walk:#eef0e8;--ky-dist-rail:#8b9288;--ky-dist-gate:#1f5fa8;--ky-dist-water:#c7d5dc;--ky-dist-green:#d2dcc4;--ky-dist-wood:#c3d0b6;--ky-dist-tree:#b3c2a3;--ky-dist-floor:#2f6db0}
|
|
168
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-district:not([data-theme=light])){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}}
|
|
169
|
+
:where(html[data-theme=dark] .ky-district,.ky-district[data-theme=dark]){--ky-dist-ground:#1b2025;--ky-dist-grid:rgba(220,226,220,.16);--ky-dist-ink:#e8ece8;--ky-dist-halo:rgba(14,18,16,.85);--ky-dist-people:#fbbf24;--ky-dist-b0:#262c31;--ky-dist-b1:#2e353b;--ky-dist-b2:#394148;--ky-dist-b3:#474f57;--ky-dist-bline:rgba(220,226,220,.22);--ky-dist-road:#2b3239;--ky-dist-deck:#353d45;--ky-dist-walk:#242b31;--ky-dist-rail:#5b646c;--ky-dist-gate:#7fb2ee;--ky-dist-water:#1c2d3b;--ky-dist-green:#202d26;--ky-dist-wood:#1c2b23;--ky-dist-tree:#2e4337;--ky-dist-floor:#7fb0e8}
|
|
146
170
|
.ky-district .ky-dist-ground{fill:var(--ky-dist-ground)}
|
|
147
171
|
.ky-district .ky-dist-grid{fill:none;stroke:var(--ky-dist-grid);stroke-width:1}
|
|
172
|
+
.ky-district .ky-dist-block{stroke:var(--ky-dist-bline);stroke-width:.5;stroke-linejoin:round}
|
|
173
|
+
.ky-district .ky-dist-b0{fill:var(--ky-dist-b0)}
|
|
174
|
+
.ky-district .ky-dist-b1{fill:var(--ky-dist-b1)}
|
|
175
|
+
.ky-district .ky-dist-b2{fill:var(--ky-dist-b2)}
|
|
176
|
+
.ky-district .ky-dist-b3{fill:var(--ky-dist-b3)}
|
|
177
|
+
.ky-district .ky-dist-street{stroke-linejoin:round;stroke-linecap:butt}
|
|
178
|
+
.ky-district .ky-dist-street-0{stroke:var(--ky-dist-road)}
|
|
179
|
+
.ky-district .ky-dist-street-1{stroke:var(--ky-dist-deck)}
|
|
180
|
+
.ky-district .ky-dist-street-2{stroke:var(--ky-dist-rail);stroke-dasharray:6 3}
|
|
181
|
+
.ky-district .ky-dist-street-3{stroke:var(--ky-dist-walk)}
|
|
182
|
+
.ky-district .ky-dist-land{stroke:none}
|
|
183
|
+
.ky-district .ky-dist-water{fill:var(--ky-dist-water)}
|
|
184
|
+
.ky-district .ky-dist-green{fill:var(--ky-dist-green)}
|
|
185
|
+
.ky-district .ky-dist-wood{fill:var(--ky-dist-wood)}
|
|
186
|
+
.ky-district .ky-dist-tree{fill:none;stroke:var(--ky-dist-tree);stroke-linecap:round}
|
|
187
|
+
.ky-district .ky-dist-gate rect{fill:var(--ky-dist-gate);stroke:var(--ky-dist-gate);stroke-width:2.4}
|
|
188
|
+
.ky-district .ky-dist-gate-plausible rect{fill:var(--ky-dist-ground);fill-opacity:.85}
|
|
189
|
+
.ky-district .ky-dist-gate-queue{fill:none;stroke:var(--ky-dist-gate);stroke-width:3;stroke-dasharray:9 7}
|
|
190
|
+
.ky-district .ky-dist-ghost{fill:var(--ky-dist-b1);fill-opacity:.35}
|
|
191
|
+
.ky-district .ky-dist-floor{stroke:var(--ky-dist-floor);stroke-width:2}
|
|
148
192
|
.ky-district .ky-dist-ink{fill:none;stroke:var(--ky-dist-ink);stroke-linecap:round;stroke-linejoin:round}
|
|
149
193
|
.ky-district .ky-dist-text{fill:var(--ky-dist-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600;paint-order:stroke;stroke:var(--ky-dist-halo);stroke-linejoin:round}`;
|
|
150
194
|
const ROOT = /^<svg([^>]*)>/;
|
|
@@ -179,6 +223,11 @@ export function surfaceCentre(options) {
|
|
|
179
223
|
if (options.sport === "mlb")
|
|
180
224
|
return [0, 0];
|
|
181
225
|
const stands = crowdStands(options);
|
|
226
|
+
// A room with no crowd (a dojo of a floor of rooms) has its mats from the corner of its drawing.
|
|
227
|
+
if (stands === undefined && options.sport === "dojo" && options.dojo !== undefined) {
|
|
228
|
+
const floor = dojo(options.dojo);
|
|
229
|
+
return [(floor.width * FEET_PER_METRE) / 2, (floor.length * FEET_PER_METRE) / 2];
|
|
230
|
+
}
|
|
182
231
|
if (stands === undefined)
|
|
183
232
|
throw new Error("a district's venue has a crowd");
|
|
184
233
|
return [stands.box.x + stands.box.w / 2, stands.box.y + stands.box.h / 2];
|
|
@@ -191,7 +240,7 @@ export function scaleOf(view) {
|
|
|
191
240
|
/** The distance a scale says, as it is read: metres under a kilometre, else kilometres. */
|
|
192
241
|
export const metresText = (metres) => (metres >= 1000 ? `${metres / 1000} km` : `${metres} m`);
|
|
193
242
|
/** The id of a district's drawing: the option's, or a hash of the options. */
|
|
194
|
-
export const districtId = (options) => (typeof options.id === "string" && options.id !== "" ? options.id : `ky-district-${hash(JSON.stringify([options.venues, options.mode, options.turn, options.language, options.theme, options.stands]))}`);
|
|
243
|
+
export const districtId = (options) => (typeof options.id === "string" && options.id !== "" ? options.id : `ky-district-${hash(JSON.stringify([options.venues, options.mode, options.turn, options.language, options.theme, options.stands, ...(options.context === undefined || options.context.length === 0 ? [] : [options.context.map((a) => a.key)]), ...(options.building === undefined ? [] : [options.building]), ...(options.floor === undefined ? [] : [`floor${options.floor}`])]))}`);
|
|
195
244
|
/** The title and the description of a district, in its language: what is drawn, where each venue stands from the first, how far apart they are, and the credit. */
|
|
196
245
|
export function describeDistrict(options, plan = planDistrict(options)) {
|
|
197
246
|
const language = options.language === "ja" ? "ja" : "en";
|
|
@@ -204,18 +253,25 @@ export function describeDistrict(options, plan = planDistrict(options)) {
|
|
|
204
253
|
const nameOf = (key) => plan.members.find((p) => p.member.key === key).member.name[language];
|
|
205
254
|
const apart = first === undefined ? "" : language === "ja" ? `${gap}${nameOf(first.a)}と${nameOf(first.b)}の中心は約${Math.round(first.metres)}m離れています。` : `${gap}The middles of ${nameOf(first.a)} and ${nameOf(first.b)} are about ${Math.round(first.metres)} metres apart.`;
|
|
206
255
|
const text = fillWords(words.district, { count: String(plan.members.length), mode: words[plan.mode === "city" ? "districtCity" : "districtNeighbours"] });
|
|
207
|
-
|
|
256
|
+
const level = options.outside?.level ?? 0;
|
|
257
|
+
const standing = level > 0 ? Math.round(plan.members.filter((p) => walkIdFor([p.member.id]) !== undefined).reduce((sum, p) => sum + p.member.seats, 0) * level / 100) : 0;
|
|
258
|
+
const crowd = standing > 0 ? `${gap}${fillWords(words.districtOutside, { percent: String(level), people: String(standing).replace(/\B(?=(\d{3})+(?!\d))/g, ",") })}` : "";
|
|
259
|
+
const credits = [...new Set([...(plan.context?.attribution[language] ?? []), ...plan.members.flatMap((p) => (p.member.attribution === undefined ? [] : [p.member.attribution[language]]))])];
|
|
260
|
+
const credit = credits.length === 0 ? "" : `${gap}${credits.join(language === "ja" ? "。" : "; ")}${language === "ja" ? "。" : "."}`;
|
|
261
|
+
return { title, description: `${text}${apart}${crowd}${gap}${words.districtCredit}${gap}${VENUE_ATTRIBUTION}${language === "ja" ? "。" : "."}${credit}` };
|
|
208
262
|
}
|
|
209
263
|
const fillWords = (text, values) => text.replace(/\{(\w+)\}/g, (_, key) => values[key] ?? "");
|
|
210
264
|
/** How a venue is drawn for a district: its own options with the district's language, theme and ids, in 2D with its stands or as the floor of the 3D view (its building only). */
|
|
211
|
-
export function drawMember(member, options, index, id, how = {}) {
|
|
265
|
+
export function drawMember(member, options, index, id, how = {}, partIndex = 0) {
|
|
212
266
|
const stands = options.stands !== false && how.floor !== true;
|
|
267
|
+
const own = member.parts?.[partIndex]?.options ?? member.options;
|
|
213
268
|
return drawKyogijo({
|
|
214
|
-
...
|
|
215
|
-
|
|
269
|
+
...own,
|
|
270
|
+
// A room that is drawn without a crowd stays so.
|
|
271
|
+
crowd: own.crowd === false ? false : { real: true, ...(stands ? {} : { stands: false }) },
|
|
216
272
|
language: options.language === "ja" ? "ja" : "en",
|
|
217
273
|
...(options.theme === undefined ? {} : { theme: options.theme }),
|
|
218
|
-
id: `${id}-m${index}`,
|
|
274
|
+
id: `${id}-m${index}${partIndex === 0 ? "" : `-p${partIndex}`}`,
|
|
219
275
|
});
|
|
220
276
|
}
|
|
221
277
|
/**
|
|
@@ -253,16 +309,38 @@ export function drawDistrict(options, plan = planDistrict(options), drawing) {
|
|
|
253
309
|
parts.push(`<rect class="ky-dist-ground" x="${num(view.x)}" y="${num(view.y)}" width="${num(view.w)}" height="${num(view.h)}"/>`);
|
|
254
310
|
parts.push(`<clipPath id="${id}-clip"><rect x="${num(view.x)}" y="${num(view.y)}" width="${num(view.w)}" height="${num(view.h)}"/></clipPath>`);
|
|
255
311
|
parts.push(`<path class="ky-dist-grid" clip-path="url(#${id}-clip)" d="${grid.join("")}"/>`);
|
|
312
|
+
if (plan.context !== undefined)
|
|
313
|
+
parts.push(`<g clip-path="url(#${id}-clip)">${contextFloor(plan.context)}</g>`);
|
|
314
|
+
// The building a raised venue stands in, flat under it: pale as a ghost where it is see-through, in its tone of height where it is solid.
|
|
315
|
+
if (options.building !== "none") {
|
|
316
|
+
for (const placed of plan.members) {
|
|
317
|
+
const shell = placed.member.shell;
|
|
318
|
+
if (shell === undefined)
|
|
319
|
+
continue;
|
|
320
|
+
const tone = options.building === "solid" ? `ky-dist-b${toneOf(shell.height)}` : "ky-dist-ghost";
|
|
321
|
+
parts.push(`<path class="ky-dist-block ${tone}${placed.member.plate === undefined ? "" : " ky-dist-floor"}" data-shell="${esc(placed.member.key)}"${placed.member.plate === undefined ? "" : ` data-floor="${placed.member.plate.floor}"`} clip-path="url(#${id}-clip)" d="M${placed.footprint.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z"/>`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
256
324
|
const styles = [];
|
|
257
325
|
plan.members.forEach((placed, i) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
styles.
|
|
263
|
-
|
|
264
|
-
|
|
326
|
+
placed.parts.forEach((pp, j) => {
|
|
327
|
+
const svg = drawing === undefined ? drawMember(placed.member, options, i, id, {}, j) : drawing(placed.member, i, j);
|
|
328
|
+
const { markup, styles: own } = nestedDrawing(svg);
|
|
329
|
+
for (const block of own)
|
|
330
|
+
if (!styles.includes(block))
|
|
331
|
+
styles.push(block);
|
|
332
|
+
// The 3D view draws a raised floor in the air itself: the ground under it is left bare.
|
|
333
|
+
if (drawing !== undefined && (pp.part.elevation ?? 0) > 0)
|
|
334
|
+
return;
|
|
335
|
+
const centre = surfaceCentre(pp.part.options);
|
|
336
|
+
parts.push(`<g class="ky-dist-venue" data-venue="${esc(placed.member.key)}"${j === 0 ? "" : ` data-part="${esc(pp.part.key)}"`} transform="translate(${num(pp.at[0])} ${num(pp.at[1])}) rotate(${num(pp.angle)}) translate(${num(-centre[0])} ${num(-centre[1])})">${markup}</g>`);
|
|
337
|
+
});
|
|
265
338
|
});
|
|
339
|
+
if (plan.gates.length > 0) {
|
|
340
|
+
// A door's bar on the outline, and a dashed stub out from it the way a queue would run; a gate a source names is filled, one that is ours is drawn hollow.
|
|
341
|
+
const bars = plan.gates.map((g) => `<g class="ky-dist-gate ky-dist-gate-${g.basis}" data-gate="${esc(g.key)}" transform="translate(${num(g.at[0])} ${num(g.at[1])}) rotate(${num(g.angle)})"><path class="ky-dist-gate-queue" d="M0 -9V-90"/><rect x="-24" y="-5" width="48" height="10" rx="3.5"/></g>`);
|
|
342
|
+
parts.push(`<g class="ky-dist-gates" aria-hidden="true">${bars.join("")}</g>`);
|
|
343
|
+
}
|
|
266
344
|
if (labels) {
|
|
267
345
|
const size = diag * (plan.mode === "city" ? 0.018 : 0.026);
|
|
268
346
|
plan.members.forEach((placed) => {
|
|
@@ -286,7 +364,7 @@ export function drawDistrict(options, plan = planDistrict(options), drawing) {
|
|
|
286
364
|
parts.push(`<g class="ky-dist-scale" aria-hidden="true"><path class="ky-dist-ink" stroke-width="${num(diag * 0.004)}" d="M${num(sx)} ${num(sy - diag * 0.01)}V${num(sy)}H${num(sx + bar)}V${num(sy - diag * 0.01)}"/><text class="ky-dist-text" x="${num(sx + bar / 2)}" y="${num(sy - diag * 0.018)}" text-anchor="middle" font-size="${num(diag * 0.022)}" stroke-width="${num(diag * 0.005)}">${metresText(metres)}</text></g>`);
|
|
287
365
|
const sheet = options.style === "none" ? "" : `<style>${KYOGIJO_DISTRICT_STYLE}</style>${styles.join("")}`;
|
|
288
366
|
const theme = options.theme === "light" || options.theme === "dark" ? ` data-theme="${options.theme}"` : "";
|
|
289
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" class="kyogijo ky-district" viewBox="${num(view.x)} ${num(view.y)} ${num(view.w)} ${num(view.h)}" role="img" aria-labelledby="${id}-t ${id}-d" data-sport="district" data-mode="${plan.mode}"${theme} lang="${language}"><title id="${id}-t">${esc(title)}</title><desc id="${id}-d">${esc(description)}</desc>${sheet}${parts.join("")}</svg>`;
|
|
367
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" class="kyogijo ky-district" viewBox="${num(view.x)} ${num(view.y)} ${num(view.w)} ${num(view.h)}" role="img" aria-labelledby="${id}-t ${id}-d" data-sport="district" data-mode="${plan.mode}"${(options.outside?.level ?? 0) > 0 ? ` data-outside="${options.outside?.level}"` : ""}${theme} lang="${language}"><title id="${id}-t">${esc(title)}</title><desc id="${id}-d">${esc(description)}</desc>${sheet}${parts.join("")}</svg>`;
|
|
290
368
|
}
|
|
291
369
|
/** The venues a district was asked for that the package has no key for: none when it can be drawn. */
|
|
292
370
|
export const unknownVenues = (venues) => venues.map((v) => (typeof v === "string" ? v.split(":")[0] : v.id)).filter((id) => !isDistrictId(id));
|