@johnmorrisdotca/kyogijo-stadiums 0.45.3 → 0.47.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 +43 -0
- package/NOTICE.md +9 -2
- package/dist/areas.d.ts +4 -0
- package/dist/areas.js +33 -1
- package/dist/cityctx.belmont-park.data.d.ts +3 -0
- package/dist/cityctx.belmont-park.data.js +1802 -0
- package/dist/cityctx.data.d.ts +10 -1
- package/dist/cityctx.data.js +11 -2
- package/dist/cityctx.js +19 -1
- package/dist/cityctx.meadowlands.data.d.ts +3 -0
- package/dist/cityctx.meadowlands.data.js +820 -0
- package/dist/cityctx.montreal-city.data.d.ts +3 -0
- package/dist/cityctx.montreal-city.data.js +2899 -0
- package/dist/cityctx.montreal-downtown.data.d.ts +3 -0
- package/dist/cityctx.montreal-downtown.data.js +3004 -0
- package/dist/cityctx.montreal-olympic.data.d.ts +3 -0
- package/dist/cityctx.montreal-olympic.data.js +2562 -0
- package/dist/cityctx.nyc-bronx.data.d.ts +3 -0
- package/dist/cityctx.nyc-bronx.data.js +1671 -0
- package/dist/cityctx.nyc-brooklyn.data.d.ts +3 -0
- package/dist/cityctx.nyc-brooklyn.data.js +3485 -0
- package/dist/cityctx.nyc-flushing.data.d.ts +3 -0
- package/dist/cityctx.nyc-flushing.data.js +865 -0
- package/dist/cityctx.nyc-midtown.data.d.ts +3 -0
- package/dist/cityctx.nyc-midtown.data.js +2438 -0
- package/dist/crowd.d.ts +26 -3
- package/dist/district.js +1 -1
- package/dist/draw.js +2 -2
- package/dist/flythrough.dom.d.ts +2 -0
- package/dist/flythrough.dom.js +2 -2
- package/dist/stadiums.d.ts +51 -7
- package/dist/stadiums.data.js +60 -0
- package/dist/stadiums.js +40 -6
- package/dist/stadiums.osm.data.js +224 -2
- package/dist/strings.core.d.ts +2 -0
- package/dist/strings.en.js +1 -1
- package/dist/strings.ja.js +1 -1
- package/dist/turn.kit.d.ts +14 -1
- package/dist/turn.kit.js +19 -0
- package/dist/turn.style.js +20 -3
- package/dist/venue.js +3 -1
- package/dist/venue.real.d.ts +45 -1
- package/dist/venue.real.js +245 -21
- package/dist/venue.ribbed.d.ts +18 -0
- package/dist/venue.ribbed.js +199 -0
- package/dist/venues/american-airlines-center.data.js +1 -1
- package/dist/venues.d.ts +2 -2
- package/dist/venues.data.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/crowd.d.ts
CHANGED
|
@@ -118,11 +118,11 @@ export interface RealShell {
|
|
|
118
118
|
/** The way north points on the page, in degrees clockwise from the top; absent where the rink's real orientation is not known. */
|
|
119
119
|
north?: number;
|
|
120
120
|
/**
|
|
121
|
-
* `fixed` is a lid; `saddle` a roof that rises at the two ends and falls at the two sides; `cable` a cable-supported roof with an opening over the surface, which a retractable fabric closes (BC Place's);
|
|
121
|
+
* `fixed` is a lid; `saddle` a roof that rises at the two ends and falls at the two sides; `barrel` a barrel vault, a round arch along the surface that stands highest over its middle line and falls to the walls at the two sides; `cable` a cable-supported roof with an opening over the surface, which a retractable fabric closes (BC Place's);
|
|
122
122
|
* `canopy` roofs over some of the stands and leaves the field open (Lumen Field's, `canopies`); `panels` a retractable roof of sections over a ballpark's field and stands, far above its walls (T-Mobile Park's, `panels`);
|
|
123
123
|
* `dome` an air-supported roof, a membrane that rises from the rim of the walls to a crown over the middle of the field (Tokyo Dome's, `dome`); `open` no roof: the walls only (Koshien Stadium's).
|
|
124
124
|
*/
|
|
125
|
-
roof: "fixed" | "saddle" | "cable" | "canopy" | "panels" | "dome" | "open";
|
|
125
|
+
roof: "fixed" | "saddle" | "barrel" | "cable" | "canopy" | "panels" | "dome" | "open";
|
|
126
126
|
/** The height of the walls, in feet, where it is not the roof's rim (a ballpark's walls stand far below its roof). */
|
|
127
127
|
wall?: number;
|
|
128
128
|
/** A `canopy` roof's pieces: the outline of each in the drawing's frame, the height of its low edge (over the field's side) and how far it rises to its high edge (over the back of the stands), in feet. */
|
|
@@ -130,6 +130,7 @@ export interface RealShell {
|
|
|
130
130
|
ring: Pt[];
|
|
131
131
|
low: number;
|
|
132
132
|
rise: number;
|
|
133
|
+
shape?: string;
|
|
133
134
|
}[];
|
|
134
135
|
/** A `panels` roof: the outline it covers when closed, its bottom and its top above the field in feet (rim and crown), and where its sections rest when it is open. */
|
|
135
136
|
panels?: {
|
|
@@ -138,12 +139,17 @@ export interface RealShell {
|
|
|
138
139
|
top: number;
|
|
139
140
|
parked: Pt[];
|
|
140
141
|
};
|
|
141
|
-
/** A `dome` roof: the outline of its rim in the drawing's frame, and the heights of the rim (`low`) and of the crown above the surface, in feet. It is held up by the air inside, so it is a smooth lens, with no struts. */
|
|
142
|
+
/** A `dome` roof: the outline of its rim in the drawing's frame, and the heights of the rim (`low`) and of the crown above the surface, in feet. It is held up by the air inside, so it is a smooth lens, with no struts. With `ribs` (the Olympic Stadium's) the shell is `count` concrete consoles with glazed panels between them and a roof of panels rising to a ring, and `low` is the ring's height. */
|
|
142
143
|
dome?: {
|
|
143
144
|
rim: Pt[];
|
|
144
145
|
low: number;
|
|
145
146
|
crown: number;
|
|
147
|
+
ribs?: {
|
|
148
|
+
count: number;
|
|
149
|
+
};
|
|
146
150
|
};
|
|
151
|
+
/** Draw the walls as brick (a ballpark's facade, T-Mobile Park's: from the brief; a plain drawing choice, a wall in a brick tone, no courses). */
|
|
152
|
+
brick?: boolean;
|
|
147
153
|
/** Draw the walls as ivy-covered (a plain drawing choice: the wall in green). */
|
|
148
154
|
ivy?: boolean;
|
|
149
155
|
/** A `cable` roof: how far its crown (the edge of the opening) stands over its rim, in feet, and the opening, a polygon in the drawing's frame over the surface. */
|
|
@@ -160,8 +166,25 @@ export interface RealShell {
|
|
|
160
166
|
count: number;
|
|
161
167
|
height: number;
|
|
162
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* A tower that leans over the building (the Olympic Stadium's Montreal Tower): the middle of its base and of its top in the drawing's frame, the height of its top above the surface in feet, and the width of its shaft at the
|
|
171
|
+
* base and at the top in feet (a plain tapering square shaft: its width is ours).
|
|
172
|
+
*/
|
|
173
|
+
tower?: {
|
|
174
|
+
base: Pt;
|
|
175
|
+
top: Pt;
|
|
176
|
+
height: number;
|
|
177
|
+
angle: number;
|
|
178
|
+
width: [number, number];
|
|
179
|
+
};
|
|
163
180
|
/** The height of the roof's lowest edge above the surface, in feet. */
|
|
164
181
|
edge: number;
|
|
182
|
+
/** Towers that stand apart from the bowl (Lumen Field's, at the open end), each a box on its outline in the drawing's frame, `height` feet high; `face` is the unit direction its scoreboard looks, towards the field. */
|
|
183
|
+
towers?: {
|
|
184
|
+
ring: Pt[];
|
|
185
|
+
height: number;
|
|
186
|
+
face: Pt;
|
|
187
|
+
}[];
|
|
165
188
|
}
|
|
166
189
|
/** The stands of a real venue, worked out from its building (`venueBowl`): what `layoutOf` takes in place of a sport's usual tiers. */
|
|
167
190
|
export interface RealBowl {
|
package/dist/district.js
CHANGED
|
@@ -47,7 +47,7 @@ const stadiumMember = (stadium, config, language) => {
|
|
|
47
47
|
heading: stadiumFrameHeading(stadium),
|
|
48
48
|
basis: stadium.osm.axis.basis,
|
|
49
49
|
seats: stadiumSeats(stadium, config),
|
|
50
|
-
options: park ? { sport: "mlb", park: stadium.id, crowd: { real: true }, language } : { sport: config, stadium: stadium.id, crowd: { real: true }, language },
|
|
50
|
+
options: park ? { sport: "mlb", park: stadium.id, crowd: { real: true }, language } : { sport: config, stadium: stadium.id, ...(config === "nba" ? { arena: stadium.id } : {}), crowd: { real: true }, language },
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
53
|
/** Every key a district can be asked for: the arenas of `VENUES` and the stadiums of `STADIUMS`. */
|
package/dist/draw.js
CHANGED
|
@@ -789,8 +789,8 @@ export function describe(o) {
|
|
|
789
789
|
const venue = sport === "nhl" && league === undefined && realCrowd?.real === true ? venueById(o.arena) : undefined;
|
|
790
790
|
const realLine = venue === undefined || arenaName === undefined ? "" : `${gap}${fill(words.said.real, { arena: arenaName, seats: String(venue.seats.hockey), tiers: String(venue.tiers.count) })}${gap}${words.said[venue.osm.axis.basis === "mapped" ? "realMapped" : venue.osm.axis.basis === "building" ? "realBuilding" : "realNone"]}`;
|
|
791
791
|
const real = realStadiumOf(o, sport, realCrowd?.real === true);
|
|
792
|
-
const stadiumKey = (basis, kind) => basis === "opening" ? "stadiumOpening" : basis === "pitch" ? (kind === "park" ? "stadiumPitchPark" : "stadiumPitch") : basis === "infield" ? "stadiumInfield" : basis === "building" ? (kind === "hall" ? "stadiumRingBuilding" : "stadiumBuilding") : kind === "park" ? "stadiumNonePark" : "stadiumNone";
|
|
793
|
-
const stadiumLine = real === undefined ? "" : `${gap}${fill(real.config === "mlb" ? words.said.stadiumPark : stadiumKind(real.stadium) === "hall" ? words.said.stadiumHall : words.said.stadium, { stadium: stadiumNames(real.stadium)[language], seats: String(stadiumSeats(real.stadium, real.config)), tiers: String(real.stadium.tiers.count) })}${gap}${words.said[stadiumKey(real.stadium.osm.axis.basis, stadiumKind(real.stadium))]}`;
|
|
792
|
+
const stadiumKey = (basis, kind, config) => basis === "opening" ? "stadiumOpening" : basis === "pitch" ? (kind === "park" ? "stadiumPitchPark" : "stadiumPitch") : basis === "infield" ? "stadiumInfield" : basis === "streets" ? "stadiumStreets" : basis === "building" ? (kind === "hall" ? (config === "nba" ? "stadiumCourtBuilding" : "stadiumRingBuilding") : "stadiumBuilding") : kind === "park" ? "stadiumNonePark" : "stadiumNone";
|
|
793
|
+
const stadiumLine = real === undefined ? "" : `${gap}${fill(real.config === "mlb" ? words.said.stadiumPark : stadiumKind(real.stadium) === "hall" ? words.said.stadiumHall : words.said.stadium, { stadium: stadiumNames(real.stadium)[language], seats: String(stadiumSeats(real.stadium, real.config)), tiers: String(real.stadium.tiers.count) })}${gap}${words.said[stadiumKey(real.stadium.osm.axis.basis, stadiumKind(real.stadium), real.config)]}`;
|
|
794
794
|
const parkLine = (parkName === undefined ? `${groundLine}${halfLine}${arenaLine}${realLine}${stadiumLine}` : `${gap}${fill(words.said.park, { park: parkName })}${officially(parkOfficial)}${ballpark === undefined ? "" : ballparkSentences(ballpark).map((line) => `${gap}${line}`).join("")}${stadiumLine}`) + (special === undefined ? "" : `${gap}${special.text}`);
|
|
795
795
|
// A drawing of a past season says which: the markings are those of that season.
|
|
796
796
|
const dated = typeof o.year === "number" && Number.isFinite(o.year);
|
package/dist/flythrough.dom.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface FlythroughOptions<M extends {
|
|
|
38
38
|
language?: Language;
|
|
39
39
|
/** `light` or `dark`; the device's setting decides when it is left out. */
|
|
40
40
|
theme?: "light" | "dark";
|
|
41
|
+
/** Whether the building has its roof (true when left out): the roof is there over the arena and lifts away as the camera comes into the bowl. `false` keeps the roof off for the whole flight, the walls still standing, so that the bowl is seen from the first stage of the arena. */
|
|
42
|
+
roof?: boolean;
|
|
41
43
|
}
|
|
42
44
|
/** How it behaves. */
|
|
43
45
|
export interface FlythroughSettings {
|
package/dist/flythrough.dom.js
CHANGED
|
@@ -93,7 +93,7 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
93
93
|
const drawing = { sport: "nhl", arena: venue.id, crowd: { real: true }, language, ...(options.theme === undefined ? {} : { theme: options.theme }) };
|
|
94
94
|
let turn;
|
|
95
95
|
// The settings object is kept: the view reads its `lens` at every paint, and the path shortens the lens as the camera comes into the bowl.
|
|
96
|
-
const turnSettings = { preset: "overhead", controls: false, venue: { roof: true }, motion: "none", id: `${id}-turn`, lens: LENS };
|
|
96
|
+
const turnSettings = { preset: "overhead", controls: false, venue: { roof: options.roof !== false, walls: true }, motion: "none", id: `${id}-turn`, lens: LENS };
|
|
97
97
|
if (stageList.some((one) => one === "arena" || one === "bowl" || one === "rink" || one === "game"))
|
|
98
98
|
turn = mountKyogijoTurn(turnHost, drawing, turnSettings);
|
|
99
99
|
// ---- The game: a plane that lies on the floor of the 3D arena, with the play's dots drawn on it by the replay's own layers. It takes the floor's own transform after every paint, and stands in the same layer of the stack
|
|
@@ -228,7 +228,7 @@ export function mountFlythrough(host, options, settings = {}) {
|
|
|
228
228
|
if (live3d && turn !== undefined) {
|
|
229
229
|
put("3d", turnHost, "opacity", num(cam.arena, 3));
|
|
230
230
|
put("3d", turnHost, "--kf-roof", num(cam.roof, 3));
|
|
231
|
-
put("3d", turnHost, "@data-roof", cam.roof > 0.004 ? "1" : "0");
|
|
231
|
+
put("3d", turnHost, "@data-roof", options.roof !== false && cam.roof > 0.004 ? "1" : "0");
|
|
232
232
|
const view = `${num(cam.yaw, 3)}|${num(cam.pitch, 3)}|${num(cam.logZoom, 4)}|${num(cam.logLens, 4)}`;
|
|
233
233
|
if (written["3d:view"] !== view) {
|
|
234
234
|
written["3d:view"] = view;
|
package/dist/stadiums.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { VENUE_ATTRIBUTION } from "./venues.ts";
|
|
|
5
5
|
export { VENUE_ATTRIBUTION };
|
|
6
6
|
/** How a fact is known: `documented` is what a source cited with it says; `plausible` is ours, for a bowl no source describes. */
|
|
7
7
|
export type StadiumBasis = "documented" | "plausible";
|
|
8
|
-
/** The configurations of a stadium: the field the seats are counted for. `cfl` and `nfl` are the football fields of the two leagues, `soccer` a soccer pitch, `mlb` a baseball field, `boxing` a boxing ring in a hall. */
|
|
9
|
-
export type StadiumConfigId = "cfl" | "nfl" | "soccer" | "mlb" | "boxing";
|
|
8
|
+
/** The configurations of a stadium: the field the seats are counted for. `cfl` and `nfl` are the football fields of the two leagues, `soccer` a soccer pitch, `mlb` a baseball field, `boxing` a boxing ring in a hall, `nba` a basketball court in a hall (an arena that stages basketball, Barclays Center). */
|
|
9
|
+
export type StadiumConfigId = "cfl" | "nfl" | "soccer" | "mlb" | "boxing" | "nba";
|
|
10
10
|
/** What a stadium is: a `stadium` has a football field and a soccer pitch lying along its length, a `park` is a baseball park, whose frame is home plate's, a `hall` is a hall with a ring in the middle and the seats round it. */
|
|
11
11
|
export type StadiumKind = "stadium" | "park" | "hall";
|
|
12
12
|
/**
|
|
@@ -60,6 +60,14 @@ export interface RealStadiumData {
|
|
|
60
60
|
looks?: {
|
|
61
61
|
ivyWalls?: string;
|
|
62
62
|
dirtInfield?: string;
|
|
63
|
+
brickWalls?: string;
|
|
64
|
+
};
|
|
65
|
+
/** Which way a ballpark's field lies where no map says but a published list does: the heading from home plate to centre field in degrees clockwise from north, `streets` (the streets the list names at the bases and the outfield), where it was read and what was read. */
|
|
66
|
+
orientation?: {
|
|
67
|
+
heading: number;
|
|
68
|
+
basis: "streets";
|
|
69
|
+
source: string;
|
|
70
|
+
read: string;
|
|
63
71
|
};
|
|
64
72
|
configs: Partial<Record<StadiumConfigId, StadiumConfigData>>;
|
|
65
73
|
/** The seats of the whole bowl where it holds more than a configuration's (Lumen Field's soccer seats are fewer: the upper sections are closed): the bowl is fitted to these and a configuration opens its seats from the lowest bowl up. */
|
|
@@ -80,6 +88,14 @@ export interface RealStadiumData {
|
|
|
80
88
|
};
|
|
81
89
|
read: string;
|
|
82
90
|
};
|
|
91
|
+
/** A tower that leans over the stadium (the Olympic Stadium's Montreal Tower): its height and its inclination, as the article gives them. The tower's footprint is `StadiumOsm.tower`. */
|
|
92
|
+
tower?: {
|
|
93
|
+
height: number;
|
|
94
|
+
unit: "m";
|
|
95
|
+
angle: number;
|
|
96
|
+
basis: StadiumBasis;
|
|
97
|
+
read: string;
|
|
98
|
+
};
|
|
83
99
|
/** The building's lower band, a podium under the concourse levels: its height in feet and how it is known (ours where the sources do not say). */
|
|
84
100
|
facade?: {
|
|
85
101
|
podium: number;
|
|
@@ -87,6 +103,15 @@ export interface RealStadiumData {
|
|
|
87
103
|
basis: StadiumBasis;
|
|
88
104
|
read: string;
|
|
89
105
|
};
|
|
106
|
+
/** A tower standing apart from the bowl, `endTower` (Lumen Field's, at the open north end): the storeys the article gives, and ours (`plausible`) the height in metres, the footprint in metres, and how far it stands along the field's axis from the middle of the pitch, in metres (positive towards the heading of the axis). */
|
|
107
|
+
endTower?: {
|
|
108
|
+
storeys: number;
|
|
109
|
+
height: number;
|
|
110
|
+
size: [number, number];
|
|
111
|
+
along: number;
|
|
112
|
+
basis: StadiumBasis;
|
|
113
|
+
read: string;
|
|
114
|
+
};
|
|
90
115
|
roof: {
|
|
91
116
|
kind: StadiumRoof;
|
|
92
117
|
basis: StadiumBasis;
|
|
@@ -106,9 +131,15 @@ export interface RealStadiumData {
|
|
|
106
131
|
};
|
|
107
132
|
/** A canopy's share of the seats it covers, as the article gives it. */
|
|
108
133
|
cover?: number;
|
|
109
|
-
/** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap). */
|
|
134
|
+
/** A retractable roof's bottom and top above the field, in feet, as the article gives them (an air-supported roof's height is the building's, from OpenStreetMap; a fixed roof's `top` is its crown's). */
|
|
110
135
|
bottom?: number;
|
|
111
136
|
top?: number;
|
|
137
|
+
/** A fixed roof on a frame of ribs (the Olympic Stadium's consoles): how many, how many of them are anchored to the ground, and where it is read. */
|
|
138
|
+
ribs?: {
|
|
139
|
+
count: number;
|
|
140
|
+
anchored: number;
|
|
141
|
+
source: string;
|
|
142
|
+
};
|
|
112
143
|
};
|
|
113
144
|
}
|
|
114
145
|
/** What OpenStreetMap gave of a stadium: ODbL 1.0 data, "(c) OpenStreetMap contributors". */
|
|
@@ -137,21 +168,34 @@ export interface StadiumOsm {
|
|
|
137
168
|
canopies?: {
|
|
138
169
|
source: string;
|
|
139
170
|
height: number;
|
|
140
|
-
rise: number;
|
|
171
|
+
rise: number; /** OpenStreetMap's `roof:shape` where the roof has one (`round` for Lumen Field's two). */
|
|
172
|
+
shape?: string;
|
|
141
173
|
ring: [number, number][];
|
|
142
174
|
}[];
|
|
175
|
+
/**
|
|
176
|
+
* The tower OpenStreetMap maps beside the building, where the stadium has one (`RealStadiumData.tower`): its footprint (the tower's shaft seen from above, with its base) as `[longitude, latitude]`, the height OpenStreetMap
|
|
177
|
+
* maps for it in metres, and the two ends of its axis worked out from them: the base, the footprint's point farthest from the middle of the building, and the top, 165 m up, which lies over the building (the shaft leans over
|
|
178
|
+
* it at the inclination the article gives, so its top is that many metres from the base, towards the middle of the building).
|
|
179
|
+
*/
|
|
180
|
+
tower?: {
|
|
181
|
+
source: string;
|
|
182
|
+
height: number;
|
|
183
|
+
ring: [number, number][];
|
|
184
|
+
base: [number, number];
|
|
185
|
+
top: [number, number];
|
|
186
|
+
};
|
|
143
187
|
/** The building's outline as `[longitude, latitude]`, not repeating its first point. */
|
|
144
188
|
ring: [number, number][];
|
|
145
189
|
/**
|
|
146
190
|
* How the field lies, worked out when the data was made: the heading of its long side (degrees clockwise from north, 0 to under 180; for a ballpark, the heading from home plate out to centre field, 0 to under 360), how that
|
|
147
191
|
* is known (`opening`: from the roof's opening, which the article says is the field's size; `pitch`: from the pitches OpenStreetMap maps, and for a ballpark from the grass of the field it maps, fitted to the fence
|
|
148
192
|
* the park publishes (`rms`, in metres, is how far the two lie apart); `infield`: from the mapped infield, with the outline choosing which way is out to centre field; `building`: from the long axis of the
|
|
149
|
-
* building's main hall, an inference; `none`: no direction is known, and the heading is a stand-in (90 for a field with a long side, and for a ballpark the way that keeps its fence and its stands inside the building)),
|
|
193
|
+
* building's main hall, an inference; `streets`: from the streets a published list gives at the bases and the outfield of a ballpark, to the nearest 45 degrees, an inference; `none`: no direction is known, and the heading is a stand-in (90 for a field with a long side, and for a ballpark the way that keeps its fence and its stands inside the building)),
|
|
150
194
|
* how much of a direction the building has (`strength`) and how much longer than wide the opening, the pitch or the hall is (`elongation`), and the middle of the field as `[longitude, latitude]` (home plate for a ballpark).
|
|
151
195
|
*/
|
|
152
196
|
axis: {
|
|
153
197
|
heading: number;
|
|
154
|
-
basis: "opening" | "pitch" | "infield" | "building" | "none";
|
|
198
|
+
basis: "opening" | "pitch" | "infield" | "building" | "streets" | "none";
|
|
155
199
|
strength: number;
|
|
156
200
|
elongation: number;
|
|
157
201
|
centre: [number, number];
|
|
@@ -191,7 +235,7 @@ export declare function stadiumNames(stadium: RealStadium): {
|
|
|
191
235
|
export declare const stadiumFrameHeading: (stadium: RealStadium) => number;
|
|
192
236
|
/** How the field lies in a stadium's building (see `StadiumOsm.axis`). */
|
|
193
237
|
export declare const stadiumOrientation: (stadium: RealStadium) => StadiumOrientation;
|
|
194
|
-
/** The configuration a sport is: `cfl`, `nfl`, `soccer`, `mlb` or `
|
|
238
|
+
/** The configuration a sport is: `cfl`, `nfl`, `soccer`, `mlb`, `boxing` or `nba`, or `undefined` for a sport a stadium has no configuration for. */
|
|
195
239
|
export declare const stadiumConfigOf: (sport: string | undefined) => StadiumConfigId | undefined;
|
|
196
240
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
197
241
|
export declare const stadiumSeats: (stadium: RealStadium, config: StadiumConfigId) => number;
|
package/dist/stadiums.data.js
CHANGED
|
@@ -22,6 +22,7 @@ export const STADIUM_DATA = [
|
|
|
22
22
|
"wikidataSeats": [67000, 72000],
|
|
23
23
|
"tiers": { "count": 3, "basis": "documented", "read": "the article names the lower concourse levels, the club and suite levels and the Summit levels above them" },
|
|
24
24
|
"roof": { "kind": "canopy", "basis": "documented", "cover": 0.7, "read": "the article: a roof of 200,000 square feet that covers 70 percent of the seats and leaves the field open, spanning 720 feet between concrete pylons at the north and south ends" },
|
|
25
|
+
"endTower": { "storeys": 13, "height": 46, "size": [12, 12], "along": 85, "basis": "plausible", "read": "the article: a 13-story tower at the north end, with a vertical scoreboard and the Hawks' Nest bleachers at its base; the storeys are the article's, and the height (46 metres, a storey of 3.5 metres), the footprint (12 by 12 metres), the place (85 metres north of the pitch's middle, on its axis, where the end is open) and the scoreboard's size on its face are ours (no source gives them)" },
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
"id": "t-mobile-park",
|
|
@@ -29,6 +30,7 @@ export const STADIUM_DATA = [
|
|
|
29
30
|
"wikidata": "Q1193117",
|
|
30
31
|
"park": "t-mobile-park",
|
|
31
32
|
"en": { "title": "T-Mobile Park", "revision": 1379419810 },
|
|
33
|
+
"looks": { "brickWalls": "the brief (John's, 2026-10-22): the ballpark's facade is brick; the English article does not say so, so it is drawn as a wall in a brick tone, nothing more (no courses, no arches)" },
|
|
32
34
|
"configs": { "mlb": { "sport": "mlb", "seats": 47368, "source": "wikipedia", "read": "the infobox's capacity for baseball, 47,368" } },
|
|
33
35
|
"wikidataSeats": [47476],
|
|
34
36
|
"tiers": { "count": 4, "basis": "documented", "tierSeats": { "lower": 20521, "upper": 22075, "bleachers": 3666 }, "read": "the article's levels: the Main Level of 20,521 seats (most of the lower bowl), the Club Level of 4,123, the Suite Level of 2,050 and the View Level of 15,902, and two bleacher sections of 3,666" },
|
|
@@ -72,4 +74,62 @@ export const STADIUM_DATA = [
|
|
|
72
74
|
"tiers": { "count": 1, "basis": "documented", "read": "the Japanese article: the stands are on one level, the largest of a one-level stand in Japan, the infield stand's 50 rows 14.3 metres high" },
|
|
73
75
|
"roof": { "kind": "open", "basis": "documented", "read": "the English article: the outfield is natural grass and open to the air, no roof over the grounds; the Ginsan roof over the grandstand behind home plate is not drawn" },
|
|
74
76
|
},
|
|
77
|
+
{
|
|
78
|
+
"id": "yankee-stadium",
|
|
79
|
+
"kind": "park",
|
|
80
|
+
"wikidata": "Q753529",
|
|
81
|
+
"park": "yankee-stadium",
|
|
82
|
+
"en": { "title": "Yankee Stadium", "revision": 1382714435 },
|
|
83
|
+
"configs": { "mlb": { "sport": "mlb", "seats": 46537, "source": "wikipedia", "read": "the infobox's capacity for baseball, 46,537 (2020 on; earlier seasons 47,309 to 50,291), the article's own figure for seats, with standing room 52,325; Wikidata's capacity (49,642) is the figure of 2014" } },
|
|
84
|
+
"wikidataSeats": [49642],
|
|
85
|
+
"tiers": { "count": 2, "basis": "documented", "read": "the article: the Field Level and Main Level are the lower bowl, with the suites on the H&R Block Level, and the Upper Level and Grandstand Level the upper bowl; bleacher seats beyond the outfield fences" },
|
|
86
|
+
"roof": { "kind": "open", "basis": "plausible", "read": "the article speaks of a roof only as the support of the top deck's cantilevers and its lighting, with a frieze along the upper deck; nothing says that anything covers the field or the lower bowl, so the drawing is open to the air and leaves the upper deck's roof out" },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "citi-field",
|
|
90
|
+
"kind": "park",
|
|
91
|
+
"wikidata": "Q1072882",
|
|
92
|
+
"park": "citi-field",
|
|
93
|
+
"en": { "title": "Citi Field", "revision": 1381363029 },
|
|
94
|
+
"configs": { "mlb": { "sport": "mlb", "seats": 41922, "source": "wikipedia", "read": "the infobox's capacity for baseball, 41,922 (2012 on; 41,800 in 2009 to 2011), which Wikidata's capacity (P1083) also gives" } },
|
|
95
|
+
"wikidataSeats": [41922],
|
|
96
|
+
"tiers": { "count": 3, "basis": "plausible", "read": "the article names the Field, the Excelsior and the Promenade levels and says restaurants and clubs are on every level; three bowls are drawn from those names (ours: it does not count them)" },
|
|
97
|
+
"roof": { "kind": "open", "basis": "documented", "read": "the article: the preliminary model of 1998 had a retractable roof and a movable grass field; the ballpark as built has no roof, and the drawing is open to the air" },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "metlife-stadium",
|
|
101
|
+
"kind": "stadium",
|
|
102
|
+
"wikidata": "Q10862290",
|
|
103
|
+
"ground": "metlife-stadium",
|
|
104
|
+
"gridiron": "nfl",
|
|
105
|
+
"en": { "title": "MetLife Stadium", "revision": 1381185974 },
|
|
106
|
+
"configs": { "nfl": { "sport": "nfl", "seats": 82500, "source": "wikipedia", "read": "the infobox's capacity, 82,500, for both the Giants' and the Jets' games, which Wikidata's capacity (P1083) also gives" }, "soccer": { "sport": "soccer", "seats": 80663, "source": "wikipedia", "read": "the infobox's capacity for the 2026 world cup of soccer, 80,663, 1,837 fewer than its first (the renovation of the lower bowl that widened the field for the tournament)" } },
|
|
107
|
+
"bowlSeats": 82500,
|
|
108
|
+
"wikidataSeats": [82500],
|
|
109
|
+
"tiers": { "count": 3, "basis": "documented", "lowerSeats": 33346, "read": "the article's table of the seating bowl: a lower bowl of 33,346 seats, a mid-bowl of 21,323 and an upper bowl of 27,897" },
|
|
110
|
+
"roof": { "kind": "open", "basis": "documented", "read": "the article: MetLife Stadium does not have a roof, as proposals to include one failed over a dispute for funding" },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "olympic-stadium-montreal",
|
|
114
|
+
"kind": "park",
|
|
115
|
+
"wikidata": "Q285467",
|
|
116
|
+
"park": "olympic-stadium-montreal",
|
|
117
|
+
"en": { "title": "Olympic Stadium (Montreal)", "revision": 1382141337 },
|
|
118
|
+
"configs": { "mlb": { "sport": "mlb", "seats": 49757, "source": "wikipedia", "read": "the infobox's capacity for baseball, 49,757 (1992 on, after 12,000 seats were taken out and home plate moved closer to the stands in the renovations of 1991); Wikidata's capacity (45,757) is kept beside it" } },
|
|
119
|
+
"wikidataSeats": [45757],
|
|
120
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source read says how many seating levels there are: a lower and an upper bowl are drawn, the usual for a stadium of 50,000 seats (ours)" },
|
|
121
|
+
"roof": { "kind": "fixed", "basis": "documented", "read": "the article: the Kevlar roof, which could retract, was removed in May 1998 and a $26 million non-retractable opaque blue roof was installed later that year; a doughnut-shaped permanent ring is the roof's permanent component; the roof is only 52 m (170.6 ft) over the field of play (the English article) and the building 284 m by 245 m (the French article), the roof's profile, the glazed bays and the ribs' flare are ours, from photographs", "top": 171, "ribs": { "count": 38, "anchored": 34, "source": "https://fr.wikipedia.org/wiki/Stade_olympique_de_Montr%C3%A9al: the frame is 38 cantilevered consoles, 34 anchored to the ground and 4 fixed to the tower" } },
|
|
122
|
+
"tower": { "height": 165, "unit": "m", "angle": 45, "basis": "documented", "read": "the article: the Montreal Tower, 165 m high, the tallest inclined structure in the world, inclined at 45 degrees and built into the north base of the stadium, from which the cables of the roof were hung; OpenStreetMap maps the building's outline and the same 165 m height, the observatory at its top (a viewpoint) and, as the part of the funicular line that rides on the tower (tagged above ground, its tunnel below), the line of the shaft from its foot to its top: that is 158 m across for 165 m of height, the article's 45 degrees. The slight curve of the shaft and its widths are ours." },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "barclays-center",
|
|
126
|
+
"kind": "hall",
|
|
127
|
+
"wikidata": "Q807966",
|
|
128
|
+
"names": { "en": "Barclays Center", "ja": "バークレイズ・センター" },
|
|
129
|
+
"en": { "title": "Barclays Center", "revision": 1380399342 },
|
|
130
|
+
"configs": { "nba": { "sport": "nba", "seats": 17732, "source": "wikipedia", "read": "the infobox's capacity for basketball, 17,732, which Wikidata's capacity (P1083) also gives (hockey 15,795, which the NHL's Islanders used until 2020)" } },
|
|
131
|
+
"wikidataSeats": [17732],
|
|
132
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source read says how many seating levels there are: a lower and an upper bowl are drawn, the usual for an arena of 17,700 seats (ours)" },
|
|
133
|
+
"roof": { "kind": "fixed", "basis": "documented", "read": "the article speaks of the arena's green roof (an ironworker was killed installing it in 2015) and of a weathering-steel latticework in three articulated bands; the roof is fixed" },
|
|
134
|
+
},
|
|
75
135
|
];
|
package/dist/stadiums.js
CHANGED
|
@@ -55,8 +55,8 @@ const lonLat = (ring) => ring.map(([lon, lat]) => [lon, lat]);
|
|
|
55
55
|
export const stadiumFrameHeading = (stadium) => (stadiumKind(stadium) === "park" ? (stadium.osm.axis.heading + 90) % 360 : stadium.osm.axis.heading);
|
|
56
56
|
/** How the field lies in a stadium's building (see `StadiumOsm.axis`). */
|
|
57
57
|
export const stadiumOrientation = (stadium) => ({ heading: stadium.osm.axis.heading, basis: stadium.osm.axis.basis, north: round(northOn(stadiumFrameHeading(stadium))) });
|
|
58
|
-
/** The configuration a sport is: `cfl`, `nfl`, `soccer`, `mlb` or `
|
|
59
|
-
export const stadiumConfigOf = (sport) => (sport === "cfl" || sport === "nfl" || sport === "soccer" || sport === "mlb" || sport === "boxing" ? sport : undefined);
|
|
58
|
+
/** The configuration a sport is: `cfl`, `nfl`, `soccer`, `mlb`, `boxing` or `nba`, or `undefined` for a sport a stadium has no configuration for. */
|
|
59
|
+
export const stadiumConfigOf = (sport) => (sport === "cfl" || sport === "nfl" || sport === "soccer" || sport === "mlb" || sport === "boxing" || sport === "nba" ? sport : undefined);
|
|
60
60
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
61
61
|
export const stadiumSeats = (stadium, config) => stadium.configs[config]?.seats ?? 0;
|
|
62
62
|
/**
|
|
@@ -168,6 +168,17 @@ export function planStadium(stadium, config, box) {
|
|
|
168
168
|
mine.set(key, made);
|
|
169
169
|
return made;
|
|
170
170
|
}
|
|
171
|
+
/** A tower of a stadium, in the drawing's frame: its outline (a rectangle with its sides along the field's axes), its height in feet and the direction its scoreboard looks, along an axis, towards the middle of the field. */
|
|
172
|
+
function endTowerOf(tower, heading, middle, place, at) {
|
|
173
|
+
const [dx, dy] = turnedTo(heading, [Math.sin((heading * Math.PI) / 180), Math.cos((heading * Math.PI) / 180)]);
|
|
174
|
+
const c = [middle[0] + dx * tower.along, middle[1] + dy * tower.along];
|
|
175
|
+
const [hw, hh] = [tower.size[0] / 2, tower.size[1] / 2];
|
|
176
|
+
const ring = place([[c[0] - hw, c[1] - hh], [c[0] + hw, c[1] - hh], [c[0] + hw, c[1] + hh], [c[0] - hw, c[1] + hh]]);
|
|
177
|
+
const [mx, my] = [(ring[0][0] + ring[2][0]) / 2, (ring[0][1] + ring[2][1]) / 2];
|
|
178
|
+
const [fx, fy] = [at[0] - mx, at[1] - my];
|
|
179
|
+
const face = Math.abs(fx) >= Math.abs(fy) ? [Math.sign(fx), 0] : [0, Math.sign(fy)];
|
|
180
|
+
return { ring, height: round(tower.height * FEET_PER_METRE), face };
|
|
181
|
+
}
|
|
171
182
|
function plan(stadium, config, rink) {
|
|
172
183
|
const orientation = stadiumOrientation(stadium);
|
|
173
184
|
const origin = middleOf(lonLat(stadium.osm.ring));
|
|
@@ -292,9 +303,11 @@ function plan(stadium, config, rink) {
|
|
|
292
303
|
const mapped = roof?.height === undefined ? 0 : round((roof.height - (roof.rise ?? 0)) * FEET_PER_METRE);
|
|
293
304
|
const edge = stadium.roof.kind === "canopy" ? round(top + CANOPY_CLEARANCE) : Math.max(mapped, top + STADIUM_CLEARANCE);
|
|
294
305
|
const cable = stadium.roof.kind === "cable" && opening !== undefined && rise > 0;
|
|
295
|
-
const canopies = stadium.roof.kind === "canopy" && stadium.osm.canopies !== undefined ? stadium.osm.canopies.map((c) => ({ ring: place(turned(c.ring)), low: round((c.height - c.rise) * FEET_PER_METRE), rise: round(c.rise * FEET_PER_METRE) })) : undefined;
|
|
306
|
+
const canopies = stadium.roof.kind === "canopy" && stadium.osm.canopies !== undefined ? stadium.osm.canopies.map((c) => ({ ring: place(turned(c.ring)), low: round((c.height - c.rise) * FEET_PER_METRE), rise: round(c.rise * FEET_PER_METRE), ...(c.shape === undefined ? {} : { shape: c.shape }) })) : undefined;
|
|
296
307
|
const north = orientation.basis === "none" ? undefined : orientation.north;
|
|
297
308
|
const room = north === undefined ? 0 : NORTH_ROOM;
|
|
309
|
+
// A tower apart from the bowl: a box `along` metres from the middle of the pitch on its axis, its scoreboard on the face that looks at the field.
|
|
310
|
+
const towers = stadium.endTower === undefined ? undefined : [endTowerOf(stadium.endTower, orientation.heading, middle, place, at)];
|
|
298
311
|
const bowl = {
|
|
299
312
|
rings: closing.rings,
|
|
300
313
|
...(closing.sectors.length === 0 ? {} : { closedSectors: closing.sectors }),
|
|
@@ -304,12 +317,13 @@ function plan(stadium, config, rink) {
|
|
|
304
317
|
footprint,
|
|
305
318
|
hall: { ...outer, x0: outer.x0 - HALL, y0: outer.y0 - HALL, x1: outer.x1 + HALL, y1: outer.y1 + HALL, r: outer.r + HALL },
|
|
306
319
|
...(north === undefined ? {} : { north }),
|
|
307
|
-
roof: cable ? "cable" : canopies === undefined ? "fixed" : "canopy",
|
|
320
|
+
roof: cable ? "cable" : canopies === undefined ? (stadium.roof.kind === "open" ? "open" : "fixed") : "canopy",
|
|
308
321
|
edge,
|
|
309
322
|
...(cable ? { crown: { rise, opening } } : {}),
|
|
310
323
|
...(stadium.facade === undefined ? {} : { podium: stadium.facade.podium }),
|
|
311
324
|
...(cable && stadium.roof.masts !== undefined ? { masts: { count: stadium.roof.masts.count, height: round(stadium.roof.masts.height * FEET_PER_METRE) } } : {}),
|
|
312
325
|
...(canopies === undefined ? {} : { canopies }),
|
|
326
|
+
...(towers === undefined ? {} : { towers }),
|
|
313
327
|
},
|
|
314
328
|
};
|
|
315
329
|
return { stadium, config, orientation, box: rink, footprint, opening, bowl, seats: rings.map((ring) => ({ tier: ring.tier, seats: ring.seats })), total, open, seatArea, fit: tight ? "tight" : "fits" };
|
|
@@ -325,6 +339,13 @@ const PARK_PLANS = new WeakMap();
|
|
|
325
339
|
const PARK_COVER = 0.92;
|
|
326
340
|
/** How far an open-air ballpark's walls stand over its top row, in feet (ours). */
|
|
327
341
|
const OPEN_WALL_OVER = 6;
|
|
342
|
+
/** How far a fixed roof's crown stands over its rim, in feet (where no source gives its height), and the width of a leaning tower's shaft at its base and at its top, in feet (ours: the sources give a roof's kind and a tower's height, not these). */
|
|
343
|
+
const FIXED_CROWN = 30;
|
|
344
|
+
const TOWER_WIDTH = [240, 64];
|
|
345
|
+
/** How far in the outline the stands of a ribbed shell are fitted, as a share of the way to its middle: the shell's sweep clears the top row there (ours, from the shell's profile in `venue.ribbed.ts`). */
|
|
346
|
+
const RIBBED_STANDS = 0.87;
|
|
347
|
+
/** How far a ribbed shell's ring stands over the top row, in feet: the roof panels climb to it from the glazing, and the top row's back wall under them stays below the panel there (ours). */
|
|
348
|
+
const RIBBED_CLEARANCE = 36;
|
|
328
349
|
const PARK_PARKED = 0.3;
|
|
329
350
|
/**
|
|
330
351
|
* The plan of a ballpark from what its stands are laid from (`input`: the park's fence, the backstop and the crowd): the usual stands with each band as deep as its seats need, the building's outline turned so that home
|
|
@@ -361,6 +382,10 @@ function park(stadium, input) {
|
|
|
361
382
|
};
|
|
362
383
|
const footprint = world.map(frameOf);
|
|
363
384
|
const fb = boxOf(footprint);
|
|
385
|
+
// A ribbed shell (the Olympic Stadium's) rises over the bowl in a sweep from its outline in to a ring, so the stands must stand inside the shell: they are fitted inside the outline drawn in by `RIBBED_STANDS`.
|
|
386
|
+
const ribbed = stadium.roof.kind === "fixed" && stadium.roof.ribs !== undefined;
|
|
387
|
+
const mean = [footprint.reduce((t, p) => t + p[0], 0) / footprint.length, footprint.reduce((t, p) => t + p[1], 0) / footprint.length];
|
|
388
|
+
const fitted = ribbed ? footprint.map((p) => [mean[0] + (p[0] - mean[0]) * RIBBED_STANDS, mean[1] + (p[1] - mean[1]) * RIBBED_STANDS]) : footprint;
|
|
364
389
|
// Each band is as deep as holds its seats: the usual depths scaled by what the seats of the band are to what the band holds, a few rounds over (a band's seats grow with its depth). The usual depths
|
|
365
390
|
// take a seat to be 4.6 square feet of plan; a building too small for them has a steeper bowl, in which a seat takes less (the factors of `SEAT_AREAS`, as an arena's bowl), until the stands fit inside it.
|
|
366
391
|
let depths = { lower: 60, upper: 56, bleachers: 54 };
|
|
@@ -377,7 +402,7 @@ function park(stadium, input) {
|
|
|
377
402
|
depths[k] = Math.min(160, Math.max(20, next[k]));
|
|
378
403
|
stands = parkLayout({ ...input, depths });
|
|
379
404
|
}
|
|
380
|
-
if (stands.rings.every((r) => [r.region, ...(r.more ?? [])].every((o) => o.kind !== "poly" || o.points.every((p) => inside(
|
|
405
|
+
if (stands.rings.every((r) => [r.region, ...(r.more ?? [])].every((o) => o.kind !== "poly" || o.points.every((p) => inside(fitted, p)))))
|
|
381
406
|
break;
|
|
382
407
|
}
|
|
383
408
|
if (factor !== 1) {
|
|
@@ -392,7 +417,7 @@ function park(stadium, input) {
|
|
|
392
417
|
const room = north === undefined ? 0 : NORTH_ROOM;
|
|
393
418
|
const hall = { kind: "rrect", x0: fb.x0 - 30, y0: fb.y0 - 30, x1: fb.x1 + 30, y1: fb.y1 + 30, r: 0 };
|
|
394
419
|
const top = Math.max(...stands.rings.map((r) => r.height[1]));
|
|
395
|
-
const ivy = stadium.looks?.ivyWalls === undefined ? {} : { ivy: true };
|
|
420
|
+
const ivy = stadium.looks?.ivyWalls === undefined ? (stadium.looks?.brickWalls === undefined ? {} : { brick: true }) : { ivy: true };
|
|
396
421
|
let shell;
|
|
397
422
|
if (stadium.roof.kind === "dome") {
|
|
398
423
|
// An air-supported roof: the membrane over the outline OpenStreetMap maps for it, standing at the height of its rim (the building's height less the roof's own rise) and rising to the building's height.
|
|
@@ -403,6 +428,15 @@ function park(stadium, input) {
|
|
|
403
428
|
const low = round((mapped.height - mapped.rise) * FEET_PER_METRE);
|
|
404
429
|
shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "dome", edge: low, wall: low, dome: { rim, low, crown: round(mapped.height * FEET_PER_METRE) }, ...ivy };
|
|
405
430
|
}
|
|
431
|
+
else if (stadium.roof.kind === "fixed") {
|
|
432
|
+
// A fixed roof over the whole bowl (the Olympic Stadium's): a low lens over the outline, from the walls (a little over the top row) to a crown a little higher over the middle (ours: the sources say the roof is fixed, not what shape it has),
|
|
433
|
+
// and the tower that leans over it where OpenStreetMap maps one.
|
|
434
|
+
const low = round(Math.max(top + (ribbed ? RIBBED_CLEARANCE : STADIUM_CLEARANCE), (stadium.osm.height ?? 0) * FEET_PER_METRE));
|
|
435
|
+
const rim = footprint.map((p) => [c[0] + (p[0] - c[0]) * 0.98, c[1] + (p[1] - c[1]) * 0.98]);
|
|
436
|
+
const mapped = stadium.osm.tower;
|
|
437
|
+
const tower = mapped === undefined || stadium.tower === undefined ? undefined : { base: frameOf(metresFrom(origin, [mapped.base])[0]), top: frameOf(metresFrom(origin, [mapped.top])[0]), height: round(stadium.tower.height * FEET_PER_METRE), angle: stadium.tower.angle, width: [TOWER_WIDTH[0], TOWER_WIDTH[1]] };
|
|
438
|
+
shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "dome", edge: low, wall: low, dome: { rim, low, crown: stadium.roof.top === undefined ? low + FIXED_CROWN : Math.max(low + 6, stadium.roof.top), ...(stadium.roof.ribs === undefined ? {} : { ribs: { count: stadium.roof.ribs.count } }) }, ...(tower === undefined ? {} : { tower }), ...ivy };
|
|
439
|
+
}
|
|
406
440
|
else if (stadium.roof.kind === "open") {
|
|
407
441
|
// No roof: the walls stand a little over the top row.
|
|
408
442
|
const wall = round(Math.max(top + OPEN_WALL_OVER, (stadium.osm.height ?? 0) * FEET_PER_METRE));
|