@johnmorrisdotca/kyogijo-stadiums 0.28.0 → 0.31.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 +45 -0
- package/NOTICE.md +17 -5
- package/README.md +80 -107
- package/dist/crowd.d.ts +20 -2
- package/dist/crowd.park.d.ts +6 -0
- package/dist/crowd.park.js +3 -3
- package/dist/district.d.ts +3 -3
- package/dist/district.js +14 -13
- package/dist/draw.js +25 -10
- package/dist/flythrough-entry.d.ts +1 -0
- package/dist/flythrough-entry.js +1 -0
- package/dist/flythrough.dom.js +52 -6
- package/dist/flythrough.game.d.ts +36 -0
- package/dist/flythrough.game.js +145 -0
- package/dist/flythrough.markup.js +1 -0
- package/dist/flythrough.path.d.ts +6 -4
- package/dist/flythrough.path.js +26 -22
- package/dist/flythrough.style.d.ts +1 -1
- package/dist/flythrough.style.js +3 -0
- package/dist/flythrough.words.d.ts +2 -0
- package/dist/flythrough.words.js +6 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/stadiums-entry.d.ts +2 -2
- package/dist/stadiums-entry.js +2 -2
- package/dist/stadiums.d.ts +109 -28
- package/dist/stadiums.data.js +25 -0
- package/dist/stadiums.js +159 -30
- package/dist/stadiums.osm.data.js +114 -0
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +2 -2
- package/dist/turn.dom.d.ts +8 -2
- package/dist/turn.dom.js +10 -4
- package/dist/venue.d.ts +2 -0
- package/dist/venue.js +6 -5
- package/dist/venue.real.d.ts +7 -2
- package/dist/venue.real.js +124 -10
- package/dist/venues-entry.d.ts +2 -2
- package/dist/venues-entry.js +1 -1
- package/dist/venues.d.ts +14 -3
- package/dist/venues.data.js +72 -0
- package/dist/venues.geo.d.ts +22 -0
- package/dist/venues.geo.js +28 -0
- package/dist/venues.js +9 -1
- package/dist/venues.osm.data.js +366 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
package/dist/venue.real.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The building of a real venue, raised: its walls, standing on the outline OpenStreetMap gives, and its roof over the top tier, a lid that rises towards the middle, a saddle that rises at the two ends and falls at the two
|
|
2
|
-
// sides,
|
|
2
|
+
// sides, a cable-supported roof that rises to an opening over the surface which a retractable fabric closes (BC Place's, with the fabric gathered into its pod when `roofOpen` is asked), canopies over some of the
|
|
3
|
+
// stands that leave the field open (Lumen Field's), or retractable panels high over a ballpark (T-Mobile Park's, resting over its east side when `roofOpen` is asked). All are asked for with the roof
|
|
3
4
|
// (`venue: { roof: true }`) and give way to the eye as the other walls do. Pure: data in, planes out.
|
|
4
5
|
import { rayAt } from "./crowd.js";
|
|
5
6
|
import { n, shy, wall } from "./turn.kit.js";
|
|
@@ -52,10 +53,10 @@ function slopeThrough(points) {
|
|
|
52
53
|
return { x0: mx, y0: my, z0: mz, gx, gy };
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
|
-
* The walls and the roof of a real venue. The hall is the building's outline cut to the bowl's wall and a margin (`RealShell.hall`): its rim is where a ray from the middle meets the one or the other first, in `steps`
|
|
56
|
+
* The walls and the roof of a real venue (`parts` says which are made). The hall is the building's outline cut to the bowl's wall and a margin (`RealShell.hall`): its rim is where a ray from the middle meets the one or the other first, in `steps`
|
|
56
57
|
* rays. A wall stands on each stretch of the rim, up to the roof's height over it; the roof is a band from the rim in to the inner edge of the top tier (the middle stays open, as the 3D view's roofs are).
|
|
57
58
|
*/
|
|
58
|
-
export function realBuilding(real, top, centre, lift, steps, open = false) {
|
|
59
|
+
export function realBuilding(real, top, centre, lift, steps, open = false, parts = { walls: true, roof: true }) {
|
|
59
60
|
const items = [];
|
|
60
61
|
const c = [centre[0], centre[1]];
|
|
61
62
|
const rim = Array.from({ length: steps }, (_, k) => {
|
|
@@ -70,13 +71,25 @@ export function realBuilding(real, top, centre, lift, steps, open = false) {
|
|
|
70
71
|
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
71
72
|
if (long < 1)
|
|
72
73
|
return;
|
|
73
|
-
const high = lift + roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
|
|
74
|
+
const high = lift + (real.wall ?? roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
|
|
74
75
|
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="u-shell" width="${n(long)}" height="${n(high)}"/>`), centre), fadeTo: 0.02 });
|
|
75
76
|
});
|
|
76
|
-
if (planes.length > 0)
|
|
77
|
+
if (parts.walls && planes.length > 0)
|
|
77
78
|
items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
|
|
79
|
+
if (parts.roof && real.roof === "canopy" && real.canopies !== undefined) {
|
|
80
|
+
const planes = canopyRoof(real.canopies, c, lift, steps);
|
|
81
|
+
if (planes.length > 0)
|
|
82
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
83
|
+
return items;
|
|
84
|
+
}
|
|
85
|
+
if (parts.roof && real.roof === "panels" && real.panels !== undefined) {
|
|
86
|
+
const planes = panelsRoof(real.panels, c, lift, steps, open);
|
|
87
|
+
if (planes.length > 0)
|
|
88
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
89
|
+
return items;
|
|
90
|
+
}
|
|
78
91
|
const crown = real.roof === "cable" ? real.crown : undefined;
|
|
79
|
-
if (top?.hole == null && crown === undefined)
|
|
92
|
+
if (!parts.roof || (top?.hole == null && crown === undefined))
|
|
80
93
|
return items;
|
|
81
94
|
const roof = [];
|
|
82
95
|
// A cable roof rises from the rim of the hall to the opening over the surface; any other is a band from the rim in to the inner edge of the top tier.
|
|
@@ -105,20 +118,121 @@ export function realBuilding(real, top, centre, lift, steps, open = false) {
|
|
|
105
118
|
roof.push({ ...piece, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] });
|
|
106
119
|
}
|
|
107
120
|
if (crown !== undefined)
|
|
108
|
-
roof.push(...cablePod(real
|
|
121
|
+
roof.push(...cablePod(lift + real.edge + crown.rise, inner, c, steps, open));
|
|
109
122
|
if (roof.length > 0)
|
|
110
|
-
items.push({ id: "roof", kind: "roof", at: centre, height:
|
|
123
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(roof), planes: roof });
|
|
111
124
|
return items;
|
|
112
125
|
}
|
|
126
|
+
/** The top of a set of planes. */
|
|
127
|
+
const heightOf = (planes) => Math.max(...planes.flatMap((p) => [p.o[2], p.o[2] + p.u[2], p.o[2] + p.v[2], p.o[2] + p.u[2] + p.v[2]]));
|
|
128
|
+
/** The two points where a ray from `from` first enters a polygon and last leaves it (the polygon is a band round the middle, so a ray crosses it once), or `undefined` where it misses. */
|
|
129
|
+
function span(poly, from, angle) {
|
|
130
|
+
const [dx, dy] = [Math.cos(angle), Math.sin(angle)];
|
|
131
|
+
const hits = [];
|
|
132
|
+
poly.forEach(([ax, ay], i) => {
|
|
133
|
+
const [bx, by] = poly[(i + 1) % poly.length];
|
|
134
|
+
const [ex, ey] = [bx - ax, by - ay];
|
|
135
|
+
const den = dx * ey - dy * ex;
|
|
136
|
+
if (Math.abs(den) < 1e-12)
|
|
137
|
+
return;
|
|
138
|
+
const t = ((ax - from[0]) * ey - (ay - from[1]) * ex) / den;
|
|
139
|
+
const u = ((ax - from[0]) * dy - (ay - from[1]) * dx) / den;
|
|
140
|
+
if (t > 1e-6 && u >= 0 && u < 1)
|
|
141
|
+
hits.push(t);
|
|
142
|
+
});
|
|
143
|
+
if (hits.length < 2)
|
|
144
|
+
return undefined;
|
|
145
|
+
const [lo, hi] = [Math.min(...hits), Math.max(...hits)];
|
|
146
|
+
return [[from[0] + dx * lo, from[1] + dy * lo], [from[0] + dx * hi, from[1] + dy * hi]];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A piece of roof between two rays: the quad `[inner a, inner b, outer b, outer a]` at the heights given, as a plane that gives way to the eye. A quad whose edges are not parallel is not flat at those heights, so
|
|
150
|
+
* with `exact` it is two triangles, each a plane through its corners.
|
|
151
|
+
*/
|
|
152
|
+
function piece(id, quad, heights, c, cables, exact = false) {
|
|
153
|
+
const [ai, bi, b, a] = quad;
|
|
154
|
+
const [ex, ey] = [b[0] - a[0], b[1] - a[1]];
|
|
155
|
+
const el = Math.hypot(ex, ey);
|
|
156
|
+
if (el < 1e-6)
|
|
157
|
+
return [];
|
|
158
|
+
const make = (name, poly, hs, cable) => {
|
|
159
|
+
const slope = slopeThrough(poly.map((p, i) => [p[0], p[1], hs[i]]));
|
|
160
|
+
const mid = [poly.reduce((s, p) => s + p[0], 0) / poly.length, poly.reduce((s, p) => s + p[1], 0) / poly.length];
|
|
161
|
+
const away = Math.hypot(mid[0] - c[0], mid[1] - c[1]) || 1;
|
|
162
|
+
const paint = (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>${cable ? `<path class="u-cable" d="M${n(corners[0][0])} ${n(corners[0][1])}L${n(corners[3][0])} ${n(corners[3][1])}"/>` : ""}`;
|
|
163
|
+
const plane = raked(name, "roof", poly, slope, [ex / el, ey / el], paint);
|
|
164
|
+
return plane === undefined ? [] : [{ ...plane, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] }];
|
|
165
|
+
};
|
|
166
|
+
if (!exact)
|
|
167
|
+
return make(id, quad, heights, cables);
|
|
168
|
+
return [...make(`${id}a`, [ai, bi, b], [heights[0], heights[1], heights[2]], false), ...make(`${id}b`, [ai, b, a], [heights[0], heights[2], heights[3]], false)];
|
|
169
|
+
}
|
|
170
|
+
/** How far a canopy's low edge, the field's side of it, stands under its high edge: the OpenStreetMap roof's `roof:height` is the rise, and the canopy rises to the back of the stands (ours: the sources do not say which way it slopes). */
|
|
171
|
+
export const CANOPY_NOTE = "low over the field, high over the back of the stands";
|
|
172
|
+
/**
|
|
173
|
+
* Canopies over some of the stands (Lumen Field's two, one each side of the field): each OpenStreetMap roof outline is cut by the rays from the middle of the field into the strips between them, the edge towards
|
|
174
|
+
* the field low and the edge towards the back of the stands `rise` higher.
|
|
175
|
+
*/
|
|
176
|
+
function canopyRoof(canopies, c, lift, steps) {
|
|
177
|
+
const planes = [];
|
|
178
|
+
canopies.forEach((canopy, ci) => {
|
|
179
|
+
const spans = Array.from({ length: steps }, (_, k) => span(canopy.ring, c, (2 * Math.PI * k) / steps));
|
|
180
|
+
for (let k = 0; k < steps; k += 1) {
|
|
181
|
+
const [s, t] = [spans[k], spans[(k + 1) % steps]];
|
|
182
|
+
if (s === undefined || t === undefined)
|
|
183
|
+
continue;
|
|
184
|
+
const [lo, hi] = [lift + canopy.low, lift + canopy.low + canopy.rise];
|
|
185
|
+
planes.push(...piece(`roof-c${ci}-${k}`, [s[0], t[0], t[1], s[1]], [lo, lo, hi, hi], c, false, true));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return planes;
|
|
189
|
+
}
|
|
190
|
+
/** The share of a retractable roof's cover that the closed roof's flat crown takes (the sections meet in the middle at the crown's height). */
|
|
191
|
+
const PANEL_CROWN = 0.3;
|
|
192
|
+
/** How thick a parked section is, in feet (ours). */
|
|
193
|
+
const PANEL_THICK = 7;
|
|
194
|
+
/**
|
|
195
|
+
* A ballpark's retractable roof: closed, a band from the rim of its cover up to a crown over the middle with the fabric laid over the crown (as the cable roof's); open, only the sections resting over the side of the
|
|
196
|
+
* building they park on, as slabs at the rim's height.
|
|
197
|
+
*/
|
|
198
|
+
function panelsRoof(panels, c, lift, steps, open) {
|
|
199
|
+
const planes = [];
|
|
200
|
+
if (!open) {
|
|
201
|
+
const rim = Array.from({ length: steps }, (_, k) => firstHit(panels.cover, c, (2 * Math.PI * k) / steps) ?? c);
|
|
202
|
+
const inner = rim.map((p) => [c[0] + (p[0] - c[0]) * PANEL_CROWN, c[1] + (p[1] - c[1]) * PANEL_CROWN]);
|
|
203
|
+
for (let k = 0; k < steps; k += 1) {
|
|
204
|
+
const [a, b] = [rim[k], rim[(k + 1) % steps]];
|
|
205
|
+
const [ai, bi] = [inner[k], inner[(k + 1) % steps]];
|
|
206
|
+
const [lo, hi] = [lift + panels.bottom, lift + panels.top];
|
|
207
|
+
planes.push(...piece(`roof-${k}`, [ai, bi, b, a], [hi, hi, lo, lo], c, false, true));
|
|
208
|
+
}
|
|
209
|
+
// The crown itself: a flat cap over the middle, in the fabric's drawing.
|
|
210
|
+
planes.push(...cablePod(lift + panels.top, inner, c, steps, false));
|
|
211
|
+
return planes;
|
|
212
|
+
}
|
|
213
|
+
// Open: the parked sections as slabs.
|
|
214
|
+
const z = lift + panels.bottom;
|
|
215
|
+
const top = z + PANEL_THICK;
|
|
216
|
+
const slab = raked("parked-top", "roof", panels.parked, { x0: panels.parked[0][0], y0: panels.parked[0][1], z0: top, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-pod" points="${pointList(corners)}"/>`);
|
|
217
|
+
if (slab !== undefined)
|
|
218
|
+
planes.push({ ...slab, id: "parked-top" });
|
|
219
|
+
panels.parked.forEach((a, i) => {
|
|
220
|
+
const b = panels.parked[(i + 1) % panels.parked.length];
|
|
221
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
222
|
+
if (long < 0.5)
|
|
223
|
+
return;
|
|
224
|
+
planes.push({ ...shy(wall(`parked-${i}`, "roof", a[0], a[1], b[0], b[1], z, top, `<rect class="u-pod" width="${n(long)}" height="${n(PANEL_THICK)}"/>`), c), fadeTo: 0.3 });
|
|
225
|
+
});
|
|
226
|
+
return planes;
|
|
227
|
+
}
|
|
113
228
|
/** The pod in the middle of a cable roof's opening, as a share of the opening's size, and how thick it is, in feet (ours: the article says the fabric gathers into a pod above the video board, not how large it is). */
|
|
114
229
|
export const POD = { share: 0.24, thick: 9 };
|
|
115
230
|
/**
|
|
116
231
|
* What lies over the opening of a cable roof: the pod in the middle (a box hung at the crown's height) and, with the roof closed, the fabric between the pod and the opening's edge, drawn thin so that the seats
|
|
117
232
|
* show through it. Open, only the pod is there: the fabric has gathered into it.
|
|
118
233
|
*/
|
|
119
|
-
function cablePod(
|
|
234
|
+
function cablePod(z, edge, c, steps, open) {
|
|
120
235
|
const planes = [];
|
|
121
|
-
const z = lift + real.edge + crown.rise;
|
|
122
236
|
const pod = edge.map((p) => [c[0] + (p[0] - c[0]) * POD.share, c[1] + (p[1] - c[1]) * POD.share]);
|
|
123
237
|
if (!open) {
|
|
124
238
|
for (let k = 0; k < steps; k += 1) {
|
package/dist/venues-entry.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { VENUE_ATTRIBUTION, VENUE_IDS, VENUES, arenaOfVenue, bowlInside, planVenue, venueBowl, venueById, venueByWikidata, venueOrientation, venueRink, venuesIn } from "./venues.ts";
|
|
2
|
-
export type { RealVenue, RealVenueData, VenueBasis, VenueDivision, VenueOrientation, VenueOsm, VenuePlan, VenueRoof } from "./venues.ts";
|
|
1
|
+
export { VENUE_ATTRIBUTION, VENUE_CONFERENCES, VENUE_DIVISIONS, VENUE_IDS, VENUES, arenaOfVenue, bowlInside, conferenceOf, planVenue, venueBowl, venueById, venueByWikidata, venueOrientation, venueRink, venuesIn, venuesInConference } from "./venues.ts";
|
|
2
|
+
export type { RealVenue, RealVenueData, VenueBasis, VenueConference, VenueDivision, VenueOrientation, VenueOsm, VenuePlan, VenueRoof } from "./venues.ts";
|
package/dist/venues-entry.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The real venues of the NHL's arenas: the outline of each building and the way its rink lies (OpenStreetMap, ODbL 1.0) and a plausible bowl from the seats, tiers and roof the sources give (Wikidata, Wikipedia).
|
|
2
|
-
export { VENUE_ATTRIBUTION, VENUE_IDS, VENUES, arenaOfVenue, bowlInside, planVenue, venueBowl, venueById, venueByWikidata, venueOrientation, venueRink, venuesIn } from "./venues.js";
|
|
2
|
+
export { VENUE_ATTRIBUTION, VENUE_CONFERENCES, VENUE_DIVISIONS, VENUE_IDS, VENUES, arenaOfVenue, bowlInside, conferenceOf, planVenue, venueBowl, venueById, venueByWikidata, venueOrientation, venueRink, venuesIn, venuesInConference } from "./venues.js";
|
package/dist/venues.d.ts
CHANGED
|
@@ -6,8 +6,15 @@ export { VENUE_ATTRIBUTION };
|
|
|
6
6
|
export type VenueBasis = "documented" | "plausible";
|
|
7
7
|
/** The kinds of roof the venues have: `fixed` is a lid over the bowl, `saddle` a roof that rises at the two ends and falls at the two sides (the Scotiabank Saddledome's). */
|
|
8
8
|
export type VenueRoof = "fixed" | "saddle";
|
|
9
|
-
/** The NHL's divisions
|
|
10
|
-
export
|
|
9
|
+
/** The NHL's divisions, in the order of the league's own listing: the Western Conference's two, then the Eastern's. A division none of whose arenas has a venue yet has no venues in `venuesIn`. */
|
|
10
|
+
export declare const VENUE_DIVISIONS: readonly ["pacific", "central", "atlantic", "metropolitan"];
|
|
11
|
+
export type VenueDivision = (typeof VENUE_DIVISIONS)[number];
|
|
12
|
+
/** The NHL's two conferences, and the divisions of each. */
|
|
13
|
+
export declare const VENUE_CONFERENCES: {
|
|
14
|
+
readonly western: readonly ["pacific", "central"];
|
|
15
|
+
readonly eastern: readonly ["atlantic", "metropolitan"];
|
|
16
|
+
};
|
|
17
|
+
export type VenueConference = keyof typeof VENUE_CONFERENCES;
|
|
11
18
|
/** The facts of a venue: the seats and the number of seating levels and the kind of roof, each with what a source says. No text of an article is kept. */
|
|
12
19
|
export interface RealVenueData {
|
|
13
20
|
/** The key of the arena in `ARENAS`: `honda-center`. */
|
|
@@ -82,7 +89,7 @@ export interface VenueOrientation {
|
|
|
82
89
|
basis: "mapped" | "building" | "none";
|
|
83
90
|
north: number;
|
|
84
91
|
}
|
|
85
|
-
/** The venues by key:
|
|
92
|
+
/** The venues by key: the arenas of the NHL's divisions that have been drawn, with their facts and their outlines. */
|
|
86
93
|
export declare const VENUES: Readonly<Record<string, RealVenue>>;
|
|
87
94
|
/** The keys of `VENUES`, in the order of the data. */
|
|
88
95
|
export declare const VENUE_IDS: readonly string[];
|
|
@@ -92,6 +99,10 @@ export declare const venueById: (id: string | undefined) => RealVenue | undefine
|
|
|
92
99
|
export declare const venueByWikidata: (wikidata: string | undefined) => RealVenue | undefined;
|
|
93
100
|
/** The venues of a division of the NHL, in the order of the data. */
|
|
94
101
|
export declare const venuesIn: (division: VenueDivision) => RealVenue[];
|
|
102
|
+
/** The conference a division of the NHL is in. */
|
|
103
|
+
export declare const conferenceOf: (division: VenueDivision) => VenueConference;
|
|
104
|
+
/** The venues of a conference of the NHL, division by division, in the order of the data. */
|
|
105
|
+
export declare const venuesInConference: (conference: VenueConference) => RealVenue[];
|
|
95
106
|
/** The arena a venue is of. */
|
|
96
107
|
export declare const arenaOfVenue: (venue: RealVenue) => Arena;
|
|
97
108
|
/** How the rink lies in a venue's building (see `VenueOsm.axis`). */
|
package/dist/venues.data.js
CHANGED
|
@@ -71,4 +71,76 @@ export const VENUE_DATA = [
|
|
|
71
71
|
"tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn" },
|
|
72
72
|
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
"id": "united-center",
|
|
76
|
+
"wikidata": "Q639975",
|
|
77
|
+
"division": "central",
|
|
78
|
+
"en": { "title": "United Center", "revision": 1380672233 },
|
|
79
|
+
"seats": { "hockey": 19717, "source": "wikipedia", "wikidata": 23500, "read": "the article's seating line for ice hockey, 19,717 (22,428 with standing room), which the infobox gives too; Wikidata's one capacity (23,500) is the concert one" },
|
|
80
|
+
"tiers": { "count": 2, "basis": "documented", "read": "the article tells of the 300 level seating bowl and of three levels of suites (down from 216 sky-boxes to 169 executive suites after 2009-10): a lower bowl and the 300 level, with the suites between as walks" },
|
|
81
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise; the article says the building is 140 feet tall, of concrete and steel" },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "ball-arena",
|
|
85
|
+
"wikidata": "Q795470",
|
|
86
|
+
"division": "central",
|
|
87
|
+
"en": { "title": "Ball Arena", "revision": 1379560431 },
|
|
88
|
+
"seats": { "hockey": 17809, "source": "wikipedia", "wikidata": 19155, "read": "the infobox's figure for hockey (given with lacrosse), 17,809; Wikidata's capacity without a sport is 19,155 and its other (17,417) is for futsal" },
|
|
89
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn" },
|
|
90
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "american-airlines-center",
|
|
94
|
+
"wikidata": "Q463345",
|
|
95
|
+
"division": "central",
|
|
96
|
+
"en": { "title": "American Airlines Center", "revision": 1379833787 },
|
|
97
|
+
"seats": { "hockey": 18532, "source": "wikipedia", "wikidata": 19200, "read": "the infobox's figure for ice hockey, 18,532 (19,323 with standing room); Wikidata's one capacity (19,200) is the basketball one" },
|
|
98
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn; the article speaks of retractable seating, which the bowl does not draw" },
|
|
99
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise; the article says the roof looks like a Quonset hut, a barrel vault, which the one kind of fixed roof does not draw" },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "grand-casino-arena",
|
|
103
|
+
"wikidata": "Q1421388",
|
|
104
|
+
"division": "central",
|
|
105
|
+
"en": { "title": "Grand Casino Arena", "revision": 1379407082 },
|
|
106
|
+
"seats": { "hockey": 17954, "source": "wikidata", "wikidata": 17954, "read": "Wikidata's capacity for ice hockey from 2012 (P1083 with P641 ice hockey and P580 2012), the later of its two (18,064 from 2000); the article's lede and infobox give 17,954 too" },
|
|
107
|
+
"tiers": { "count": 3, "basis": "documented", "read": "the article says the arena has four spectator levels, one of suites and three for general seating: three seating bowls, with the suite level as a walk between them" },
|
|
108
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "bridgestone-arena",
|
|
112
|
+
"wikidata": "Q302869",
|
|
113
|
+
"division": "central",
|
|
114
|
+
"en": { "title": "Bridgestone Arena", "revision": 1380457078 },
|
|
115
|
+
"seats": { "hockey": 17510, "source": "wikidata", "wikidata": 17510, "read": "Wikidata's capacity for ice hockey (P1083 with P641 ice hockey); the article's own line gives 17,159, 2.0 percent fewer" },
|
|
116
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are everywhere in the building: two bowls are drawn; the article speaks of upper-level seats and, at the south end only, of no mid-level seating and one level of suites" },
|
|
117
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise; the article speaks of two round roof support columns at the south end, which are not drawn" },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "enterprise-center",
|
|
121
|
+
"wikidata": "Q1067700",
|
|
122
|
+
"division": "central",
|
|
123
|
+
"en": { "title": "Enterprise Center", "revision": 1372086380 },
|
|
124
|
+
"seats": { "hockey": 18096, "source": "wikipedia", "wikidata": 18400, "read": "the infobox's figure for ice hockey, 18,096, which the article's first sentence gives too; Wikidata's one capacity (18,400) is not for hockey" },
|
|
125
|
+
"tiers": { "count": 2, "basis": "documented", "read": "the article tells of the replacement of all upper-tier seats and of the lower-tier seats in the 2017 to 2019 renovation: two seating tiers" },
|
|
126
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "delta-center",
|
|
130
|
+
"wikidata": "Q82294",
|
|
131
|
+
"division": "central",
|
|
132
|
+
"en": { "title": "Delta Center", "revision": 1379043310 },
|
|
133
|
+
"seats": { "hockey": 16020, "source": "wikipedia", "wikidata": 19911, "read": "the infobox's figure for ice hockey, 16,020, which the article's lede gives as the most for ice hockey; Wikidata's one capacity (19,911) is not for hockey" },
|
|
134
|
+
"tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn; the article speaks of a lower bowl rebuilt in 2025 and of terrace-style ledges" },
|
|
135
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "canada-life-centre",
|
|
139
|
+
"wikidata": "Q656928",
|
|
140
|
+
"division": "central",
|
|
141
|
+
"en": { "title": "Canada Life Centre", "revision": 1379717047 },
|
|
142
|
+
"seats": { "hockey": 15225, "source": "wikipedia", "wikidata": 15004, "read": "the article's seating capacity for hockey games, 15,225, which the infobox gives as the top of 8,812 to 15,225; Wikidata's one capacity (15,004) is older, before 278 premium seats were added to the upper level in 2015" },
|
|
143
|
+
"tiers": { "count": 2, "basis": "documented", "lowerShare": 0.579, "read": "the article says the lower bowl holds 8,812 and that premium seats were added to the upper level: two seating levels, the lower with 8,812 of the 15,225" },
|
|
144
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
|
|
145
|
+
},
|
|
74
146
|
];
|
package/dist/venues.geo.d.ts
CHANGED
|
@@ -5,6 +5,28 @@ export type LonLat = readonly [number, number];
|
|
|
5
5
|
export declare function metresFrom(origin: LonLat, ring: readonly LonLat[]): Pt[];
|
|
6
6
|
/** The inverse of `metresFrom`: a point given as east and north metres from `origin`, in degrees. */
|
|
7
7
|
export declare function lonLatFrom(origin: LonLat, [east, north]: Pt): LonLat;
|
|
8
|
+
/** A point as OpenStreetMap's Overpass API returns it. */
|
|
9
|
+
export interface OsmPoint {
|
|
10
|
+
lat: number;
|
|
11
|
+
lon: number;
|
|
12
|
+
}
|
|
13
|
+
/** An element as Overpass returns it with its geometry (`out geom`): a way's points, or a relation's members, each with theirs. */
|
|
14
|
+
export interface OsmElement {
|
|
15
|
+
type: string;
|
|
16
|
+
id: number;
|
|
17
|
+
geometry?: readonly OsmPoint[];
|
|
18
|
+
members?: readonly {
|
|
19
|
+
type: string;
|
|
20
|
+
ref: number;
|
|
21
|
+
role: string;
|
|
22
|
+
geometry?: readonly OsmPoint[];
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The closed outline an OpenStreetMap element is, as its points with the first repeated at the end, or `undefined`: a closed way is its own; a multipolygon relation is its outer members joined end to end into one
|
|
27
|
+
* ring (a building drawn in two or more ways); a relation with two outer rings or an outer way that does not close is not an outline (the caller says what rule it needs). Inner members are courtyards and are left out.
|
|
28
|
+
*/
|
|
29
|
+
export declare function outerRingOf(element: OsmElement): OsmPoint[] | undefined;
|
|
8
30
|
/** The mean of a ring's longitudes and latitudes: a point to measure from. */
|
|
9
31
|
export declare const middleOf: (ring: readonly LonLat[]) => LonLat;
|
|
10
32
|
/** The area of a polygon, unsigned. */
|
package/dist/venues.geo.js
CHANGED
|
@@ -9,6 +9,34 @@ export function metresFrom(origin, ring) {
|
|
|
9
9
|
export function lonLatFrom(origin, [east, north]) {
|
|
10
10
|
return [origin[0] + east / (RAD * EARTH * Math.cos(origin[1] * RAD)), origin[1] + north / (RAD * EARTH)];
|
|
11
11
|
}
|
|
12
|
+
const same = (a, b) => a.lat === b.lat && a.lon === b.lon;
|
|
13
|
+
/**
|
|
14
|
+
* The closed outline an OpenStreetMap element is, as its points with the first repeated at the end, or `undefined`: a closed way is its own; a multipolygon relation is its outer members joined end to end into one
|
|
15
|
+
* ring (a building drawn in two or more ways); a relation with two outer rings or an outer way that does not close is not an outline (the caller says what rule it needs). Inner members are courtyards and are left out.
|
|
16
|
+
*/
|
|
17
|
+
export function outerRingOf(element) {
|
|
18
|
+
if (element.type === "way") {
|
|
19
|
+
const g = element.geometry;
|
|
20
|
+
return g !== undefined && g.length > 3 && same(g[0], g[g.length - 1]) ? [...g] : undefined;
|
|
21
|
+
}
|
|
22
|
+
if (element.type !== "relation" || element.members === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
const parts = element.members.filter((m) => m.type === "way" && m.role === "outer" && m.geometry !== undefined && m.geometry.length > 1).map((m) => [...m.geometry]);
|
|
25
|
+
if (parts.length === 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
const ring = parts.shift();
|
|
28
|
+
while (!same(ring[0], ring[ring.length - 1]) && parts.length > 0) {
|
|
29
|
+
const tail = ring[ring.length - 1];
|
|
30
|
+
const next = parts.findIndex((part) => same(part[0], tail) || same(part[part.length - 1], tail));
|
|
31
|
+
if (next < 0)
|
|
32
|
+
return undefined;
|
|
33
|
+
const [part] = parts.splice(next, 1);
|
|
34
|
+
if (!same(part[0], tail))
|
|
35
|
+
part.reverse();
|
|
36
|
+
ring.push(...part.slice(1));
|
|
37
|
+
}
|
|
38
|
+
return parts.length === 0 && ring.length > 3 && same(ring[0], ring[ring.length - 1]) ? ring : undefined;
|
|
39
|
+
}
|
|
12
40
|
/** The mean of a ring's longitudes and latitudes: a point to measure from. */
|
|
13
41
|
export const middleOf = (ring) => [ring.reduce((s, p) => s + p[0], 0) / ring.length, ring.reduce((s, p) => s + p[1], 0) / ring.length];
|
|
14
42
|
/** The area of a polygon, unsigned. */
|
package/dist/venues.js
CHANGED
|
@@ -9,7 +9,11 @@ import { boxOf, edgeDistance, inside, metresFrom, middleOf, northOn, turnedTo }
|
|
|
9
9
|
import { VENUE_DATA } from "./venues.data.js";
|
|
10
10
|
import { VENUE_ATTRIBUTION, VENUE_OSM } from "./venues.osm.data.js";
|
|
11
11
|
export { VENUE_ATTRIBUTION };
|
|
12
|
-
/** The
|
|
12
|
+
/** The NHL's divisions, in the order of the league's own listing: the Western Conference's two, then the Eastern's. A division none of whose arenas has a venue yet has no venues in `venuesIn`. */
|
|
13
|
+
export const VENUE_DIVISIONS = ["pacific", "central", "atlantic", "metropolitan"];
|
|
14
|
+
/** The NHL's two conferences, and the divisions of each. */
|
|
15
|
+
export const VENUE_CONFERENCES = { western: ["pacific", "central"], eastern: ["atlantic", "metropolitan"] };
|
|
16
|
+
/** The venues by key: the arenas of the NHL's divisions that have been drawn, with their facts and their outlines. */
|
|
13
17
|
export const VENUES = Object.freeze(Object.fromEntries(VENUE_DATA.map((data) => [data.id, Object.freeze({ ...data, osm: VENUE_OSM[data.id] })])));
|
|
14
18
|
/** The keys of `VENUES`, in the order of the data. */
|
|
15
19
|
export const VENUE_IDS = Object.freeze(VENUE_DATA.map((data) => data.id));
|
|
@@ -19,6 +23,10 @@ export const venueById = (id) => (id !== undefined && Object.prototype.hasOwnPro
|
|
|
19
23
|
export const venueByWikidata = (wikidata) => Object.values(VENUES).find((venue) => venue.wikidata === wikidata);
|
|
20
24
|
/** The venues of a division of the NHL, in the order of the data. */
|
|
21
25
|
export const venuesIn = (division) => Object.values(VENUES).filter((venue) => venue.division === division);
|
|
26
|
+
/** The conference a division of the NHL is in. */
|
|
27
|
+
export const conferenceOf = (division) => VENUE_CONFERENCES.western.includes(division) ? "western" : "eastern";
|
|
28
|
+
/** The venues of a conference of the NHL, division by division, in the order of the data. */
|
|
29
|
+
export const venuesInConference = (conference) => VENUE_CONFERENCES[conference].flatMap((division) => venuesIn(division));
|
|
22
30
|
/** The arena a venue is of. */
|
|
23
31
|
export const arenaOfVenue = (venue) => {
|
|
24
32
|
const arena = arenaById(venue.id);
|