@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/NOTICE.md +9 -2
  3. package/dist/areas.d.ts +4 -0
  4. package/dist/areas.js +33 -1
  5. package/dist/cityctx.belmont-park.data.d.ts +3 -0
  6. package/dist/cityctx.belmont-park.data.js +1802 -0
  7. package/dist/cityctx.data.d.ts +10 -1
  8. package/dist/cityctx.data.js +11 -2
  9. package/dist/cityctx.js +19 -1
  10. package/dist/cityctx.meadowlands.data.d.ts +3 -0
  11. package/dist/cityctx.meadowlands.data.js +820 -0
  12. package/dist/cityctx.montreal-city.data.d.ts +3 -0
  13. package/dist/cityctx.montreal-city.data.js +2899 -0
  14. package/dist/cityctx.montreal-downtown.data.d.ts +3 -0
  15. package/dist/cityctx.montreal-downtown.data.js +3004 -0
  16. package/dist/cityctx.montreal-olympic.data.d.ts +3 -0
  17. package/dist/cityctx.montreal-olympic.data.js +2562 -0
  18. package/dist/cityctx.nyc-bronx.data.d.ts +3 -0
  19. package/dist/cityctx.nyc-bronx.data.js +1671 -0
  20. package/dist/cityctx.nyc-brooklyn.data.d.ts +3 -0
  21. package/dist/cityctx.nyc-brooklyn.data.js +3485 -0
  22. package/dist/cityctx.nyc-flushing.data.d.ts +3 -0
  23. package/dist/cityctx.nyc-flushing.data.js +865 -0
  24. package/dist/cityctx.nyc-midtown.data.d.ts +3 -0
  25. package/dist/cityctx.nyc-midtown.data.js +2438 -0
  26. package/dist/crowd.d.ts +26 -3
  27. package/dist/district.js +1 -1
  28. package/dist/draw.js +2 -2
  29. package/dist/flythrough.dom.d.ts +2 -0
  30. package/dist/flythrough.dom.js +2 -2
  31. package/dist/stadiums.d.ts +51 -7
  32. package/dist/stadiums.data.js +60 -0
  33. package/dist/stadiums.js +40 -6
  34. package/dist/stadiums.osm.data.js +224 -2
  35. package/dist/strings.core.d.ts +2 -0
  36. package/dist/strings.en.js +1 -1
  37. package/dist/strings.ja.js +1 -1
  38. package/dist/turn.kit.d.ts +14 -1
  39. package/dist/turn.kit.js +19 -0
  40. package/dist/turn.style.js +20 -3
  41. package/dist/venue.js +3 -1
  42. package/dist/venue.real.d.ts +45 -1
  43. package/dist/venue.real.js +245 -21
  44. package/dist/venue.ribbed.d.ts +18 -0
  45. package/dist/venue.ribbed.js +199 -0
  46. package/dist/venues/american-airlines-center.data.js +1 -1
  47. package/dist/venues.d.ts +2 -2
  48. package/dist/venues.data.js +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. 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);
@@ -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 {
@@ -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;
@@ -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 `boxing`, or `undefined` for a sport a stadium has no configuration for. */
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;
@@ -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 `boxing`, 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 : 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(footprint, p)))))
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));