@johnmorrisdotca/kyogijo-stadiums 0.32.0 → 0.33.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 +16 -0
- package/NOTICE.md +5 -3
- package/README.md +1 -1
- package/dist/crowd.d.ts +11 -2
- package/dist/crowd.draw.js +2 -1
- package/dist/district.d.ts +2 -2
- package/dist/district.js +2 -2
- package/dist/draw.js +5 -3
- package/dist/stadiums.d.ts +40 -13
- package/dist/stadiums.data.js +25 -0
- package/dist/stadiums.js +80 -35
- package/dist/stadiums.osm.data.js +100 -0
- package/dist/strings.d.ts +4 -0
- package/dist/strings.js +2 -2
- package/dist/surfaces/baseball.js +2 -1
- package/dist/surfaces/surface.d.ts +2 -0
- package/dist/turn.style.js +4 -3
- package/dist/types.d.ts +1 -1
- package/dist/venue.real.d.ts +2 -0
- package/dist/venue.real.js +53 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ All notable changes to this project are written here. The format follows
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.33.0] - 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Tokyo Dome City** ([the real stadiums](./docs/stadiums.md), [the district page](./docs/district.md)): **Tokyo Dome** (`park: "tokyo-dome"`, `東京ドーム`, 45,600 seats for baseball) and **Korakuen Hall** (`stadium: "korakuen-hall"` with `sport: "boxing"`, `後楽園ホール`, 1,403 seats and the balconies' standing places up to 2,005), each in the outline of its building from OpenStreetMap, side by side as a district (`["tokyo-dome", "korakuen-hall"]`).
|
|
15
|
+
- **An air-supported roof** (`StadiumRoof` `dome`, `RealShell.roof` `dome`): the 3D roof of Tokyo Dome is a lens over the whole ballpark, a pale skin in rings of pieces from the rim of the walls (26.19 metres) to the crown (56.19), the heights OpenStreetMap maps for the roof as a part of the building; it has no open state (`roofOpen` changes nothing). The roof's outline is kept with its heights (`StadiumOsm.roof.ring`).
|
|
16
|
+
- **Nothing is guessed about how Tokyo Dome's field lies.** No diamond is mapped in the dome and the building is nearly round, so the data says `none` and the drawing lies the way that fits the building (fence and stands, with home plate set so that the fair ground is centred), with no arrow to north; the build chooses that stand-in, the docs say it is not a fact, and a lead for later (the roof falls toward the outfield, which faces the garden to the north-west) is written down, not used.
|
|
17
|
+
- **A hall, a third kind of stadium** (`StadiumKind` `hall`, `StadiumConfigId` `boxing`): a ring in the middle, seats round it from a first row 8 feet from the ring's box, its names Wikidata's labels (`names`) and no ground or park of the package's; the lowest bowl holds the infobox's seats to the seat (`tiers.lowerSeats`) and the balconies are the closed upper bowl. `districtMember` calls it `kind: "hall"`.
|
|
18
|
+
- **A bowl that fits a small building** (`planPark`, `ParkPlan.seatArea`): where a ballpark's usual stands, at 4.6 square feet of plan a seat, do not fit inside its building, the bowl is made steeper (a seat takes less, as an arena's does) until they fit; T-Mobile Park's plan is unchanged.
|
|
19
|
+
- **Japanese Wikipedia as a source** (`scripts/stadiums.data.json` `ja`, `evidence`): `pnpm stadiums:fetch` keeps the revision and the sentences of a stadium's Japanese article beside the English one (`articlesJa`), and `pnpm stadiums` checks the revision and that each phrase a fact leans on is in the kept sentences.
|
|
20
|
+
- **The demos and the docs.** The stadiums page has the two as tabs (the roof, the orientation and the district link say what each is), the district page has the Tokyo Dome City preset, and the docs have a section for each venue, Tokyo Dome City in [the district page](./docs/district.md), items in [the gallery](https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/gallery-stadiums.html), two pictures, the sources in [references](./docs/references.md) and [NOTICE.md](./NOTICE.md) (the Japanese articles' revisions in [NOTICE.ja.md](./NOTICE.ja.md)). Four descriptions are new in both languages (`said.stadiumHall`, `stadiumRingBuilding`, `stadiumPitchPark`, `stadiumNonePark`).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- The development dependency `@johnmorrisdotca/dougu-sports` is 0.13.0 (was 0.2.0): its four new icons, `sport:mma`, `sport:boxing`, `sport:wrestling` and `sport:prowrestling`, are the combat sports' own, so the test of the real set no longer stands in the karate and judo icons for them, and the demo's emblem pickers list them. Kyogijo's own code and its published package are unchanged; the demo (built from this repository) shows the new icons.
|
|
25
|
+
|
|
10
26
|
## [0.32.0] - 2026-10-20
|
|
11
27
|
|
|
12
28
|
### Added
|
package/NOTICE.md
CHANGED
|
@@ -45,8 +45,8 @@ picture or code of the sources is copied; each source is named here so that the
|
|
|
45
45
|
| `arenas`: each arena's names, years, city, seats, rink sizes, Wikidata item and coordinates | The infobox and the text of the English Wikipedia article of each arena, at the revision cited below (the names with the years the article dates them, the year it opened, closed and was pulled down, the seats for ice hockey and for basketball, a rink's length and width where the article gives them in a sentence of its own); the name in Japanese from the title of its Japanese article or written for the package; the Wikidata item (CC0) that each article is linked to and its coordinates (P625) | Facts. The articles are CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); none of their text, tables or images is used |
|
|
46
46
|
| `venues`: the outline of each NHL arena's building, how its rink lies in it and the building's height (`VENUE_OSM`, `src/venues.osm.data.ts`) | OpenStreetMap, through the Overpass API: the closed way (or the relation of a building drawn in two ways) tagged with the venue's Wikidata item (and the ice, where it is mapped), read on 2026-10-20, with the raw answers kept in `data-sources/venues/2026-10-20/`; the outline is simplified to a metre, and the way the rink lies is worked out from it (`coreOf`, `src/venues.geo.ts`) | ODbL 1.0, (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright): a derived database, kept in its own file and offered under the ODbL; a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
|
|
47
47
|
| `venues`: each venue's seats for ice hockey, number of seating tiers and kind of roof (`VENUE_DATA`, `src/venues.data.ts`) | The item of Wikidata (the capacity P1083 and the sport it applies to P641) and the English Wikipedia article of each arena, at the revision cited below, in `data-sources/venues/2026-10-20/` as read; the facts are numbers and words of our own, with what was read in a few words | Wikidata's data is CC0; the facts read from the articles are facts, with the articles credited here (CC BY-SA 4.0) |
|
|
48
|
-
| `stadiums`: the outline of each stadium's building, the roof OpenStreetMap maps over it (its heights, the opening in it or its canopies), the pitches or the infield, and how the field lies, worked out from the opening, the pitches or the infield and the outline (`STADIUM_OSM`, `src/stadiums.osm.data.ts`) | OpenStreetMap, through the Overpass API: the closed way tagged with the stadium's Wikidata item and the multipolygon of its roof (BC Place), or the relation of the building, the roof parts
|
|
49
|
-
| `stadiums`: each stadium's seats for each configuration, number of seating bowls, kind of roof and the size of its opening (`STADIUM_DATA`, `src/stadiums.data.ts`) | The item of Wikidata (the capacity P1083) and the English Wikipedia article of each stadium, at the revision cited below, in `data-sources/stadiums/2026-10-20/` as read; the facts are numbers and words of our own, with what was read in a few words | Wikidata's data is CC0; the facts read from the
|
|
48
|
+
| `stadiums`: the outline of each stadium's building, the roof OpenStreetMap maps over it (its heights, the opening in it or its canopies), the pitches or the infield, and how the field lies, worked out from the opening, the pitches or the infield and the outline (`STADIUM_OSM`, `src/stadiums.osm.data.ts`) | OpenStreetMap, through the Overpass API: the closed way tagged with the stadium's Wikidata item and the multipolygon of its roof (BC Place), or the way or relation of the building, the roof parts, the pitches, the infield or the grass of the field named in `scripts/stadiums.data.json` (Lumen Field, T-Mobile Park, Tokyo Dome, Korakuen Hall), read on 2026-10-20, with the raw answers kept in `data-sources/stadiums/2026-10-20/`; the outline is simplified to a metre, and the way the field lies is worked out from the opening, the pitches or the infield (`smallestBox`, `src/venues.geo.ts`; a ballpark's way out to centre field is the one of four where its fence lies inside the outline, or, where nothing maps it, not known, and the heading is the one in which fence and stands fit the outline best (Tokyo Dome)) | ODbL 1.0, (c) OpenStreetMap contributors (https://www.openstreetmap.org/copyright): a derived database, kept in its own file and offered under the ODbL; a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
|
|
49
|
+
| `stadiums`: each stadium's seats for each configuration, number of seating bowls, kind of roof and the size of its opening (`STADIUM_DATA`, `src/stadiums.data.ts`) | The item of Wikidata (the capacity P1083, the coordinates P625 and the labels in English and Japanese that name a hall) and the English Wikipedia article of each stadium, at the revision cited below, and the Japanese article of the stadiums of Japan, at the revision cited in [NOTICE.ja.md](./NOTICE.ja.md#実在のスタジアム), in `data-sources/stadiums/2026-10-20/` as read; the facts are numbers and words of our own, with what was read in a few words (and, for a fact leaning on a sentence, the phrase the archive must hold) | Wikidata's data is CC0; the facts read from the articles are facts, with the articles credited here (CC BY-SA 4.0) |
|
|
50
50
|
| `atlas`: the clubs that played in each park, ground and arena (`tenants`: the club, its Wikidata item, its league, the years) | The occupants (P466) and home venues (P115) Wikidata lists for each place, with the start (P580) and end (P582) times of each statement, and the clubs' labels in English and Japanese, sports (P641) and leagues (P118), read with the Wikidata API and query service by `scripts/fetch-tenants.mjs` into `scripts/tenants.wikidata.json`; and the `tenants` line of the infobox of each place's English Wikipedia article, at the revision that `source.wikipedia` names for each club (155 articles, read on 2026-10-13) | Facts. Wikidata's data is CC0 (https://creativecommons.org/publicdomain/zero/1.0/), and the articles are CC BY-SA 4.0 as above; the names of clubs and the years are facts, no text, table or image of an article is used. Which clubs are tenants, and the league each is filed under, are our reading, in docs/atlas.md ("Tenants") |
|
|
51
51
|
| `parks`: the walls, quirks, backstop, warning track, dugouts and extra points of a ballpark (`walls`, `quirks`, `backstop_ft`, `track_ft`, `dugouts`, `labels`) | The infobox and the text of the same English article of each park, at the same revision as its fence (the Japanese article for the heights of the walls of the parks of Japan): the height of the wall along a stretch of the fence and the seasons it stood, the hill, overhang, poles, bullpens, ivy and basket the text describes with figures, the infobox's backstop distance, the warning track's width, the sides of the dugouts, and the further distances the text gives (Wrigley Field's wells, Sportsman's Park's and Milwaukee County Stadium's markers). The stretch each figure covers and the angle of each extra point are our reading of the article's words | Facts. The articles are CC BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); none of their text, tables or images is used |
|
|
52
52
|
| `parks`: the coordinates of each ballpark (`lat`, `lon`) and the years it opened and closed (`opened`, `closed`) | The item's coordinate location (P625), its date of official opening or inception (P1619, P571) and of official closure or dissolution (P3999, P576), read with the Wikidata API by `scripts/fetch-park-coordinates.mjs` and checked by `scripts/check-parks.mjs` (ES CON Field's opening, 2023, is read from its article) | Wikidata's data is CC0 (https://creativecommons.org/publicdomain/zero/1.0/) |
|
|
@@ -452,13 +452,15 @@ The thirty-two arenas of the NHL, read on 2026-10-20 (`pnpm venues:fetch <date>
|
|
|
452
452
|
|
|
453
453
|
## The stadiums' sources
|
|
454
454
|
|
|
455
|
-
BC Place, Lumen Field
|
|
455
|
+
BC Place, Lumen Field, T-Mobile Park, Tokyo Dome and Korakuen Hall, read on 2026-10-20 (`pnpm stadiums:fetch`; the files are in `data-sources/stadiums/2026-10-20/`: `osm-<stadium>.json` as Overpass returned it, `wikidata.json` and `wikipedia.json`). The outline of the building, its roof and the opening in it are OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, and live in `src/stadiums.osm.data.ts` alone. The seats of each configuration, the bowls, the roof's kind and the size of its opening are in `src/stadiums.data.ts`, with what each cites. No seat map of a club, a ticket seller or a federation is read or copied, and no mark of a club, a league or a tournament is drawn.
|
|
456
456
|
|
|
457
457
|
| Stadium | Wikidata item | OpenStreetMap way | English article and revision |
|
|
458
458
|
| --- | --- | --- | --- |
|
|
459
459
|
| BC Place | Q612227 | way/24705904 | BC Place, 1372065649 |
|
|
460
460
|
| Lumen Field | Q612736 | relation/2305831 | Lumen Field, 1373998493 |
|
|
461
461
|
| T-Mobile Park | Q1193117 | relation/3008332 | T-Mobile Park, 1379419810 |
|
|
462
|
+
| Tokyo Dome | Q733748 | way/20028328 | Tokyo Dome, 1379232526 |
|
|
463
|
+
| Korakuen Hall | Q4389857 | way/45977418 | Korakuen Hall, 1372211318 |
|
|
462
464
|
|
|
463
465
|
## The World Cups' articles
|
|
464
466
|
|
package/README.md
CHANGED
|
@@ -604,7 +604,7 @@ console.log(svg.includes("ky-cr-shell"), planVenue(venueById("climate-pledge-are
|
|
|
604
604
|
|
|
605
605
|
### A real stadium, and a district
|
|
606
606
|
|
|
607
|
-
`stadium: "bc-place"` draws BC Place for football or soccer under its cable roof, and Lumen Field
|
|
607
|
+
`stadium: "bc-place"` draws BC Place for football or soccer under its cable roof, and Lumen Field, T-Mobile Park, Tokyo Dome (`park`) and Korakuen Hall (`sport: "boxing"`) likewise ([stadiums](./docs/stadiums.md)); `drawDistrict({ venues: ["bc-place", "rogers-arena"] })` puts venues at their real positions, tight as neighbours or spread as a city, to turn together in 3D ([districts](./docs/district.md)).
|
|
608
608
|
|
|
609
609
|
<p align="center">
|
|
610
610
|
<picture>
|
package/dist/crowd.d.ts
CHANGED
|
@@ -112,9 +112,10 @@ export interface RealShell {
|
|
|
112
112
|
north?: number;
|
|
113
113
|
/**
|
|
114
114
|
* `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);
|
|
115
|
-
* `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`)
|
|
115
|
+
* `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`);
|
|
116
|
+
* `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).
|
|
116
117
|
*/
|
|
117
|
-
roof: "fixed" | "saddle" | "cable" | "canopy" | "panels";
|
|
118
|
+
roof: "fixed" | "saddle" | "cable" | "canopy" | "panels" | "dome" | "open";
|
|
118
119
|
/** The height of the walls, in feet, where it is not the roof's rim (a ballpark's walls stand far below its roof). */
|
|
119
120
|
wall?: number;
|
|
120
121
|
/** 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 +131,14 @@ export interface RealShell {
|
|
|
130
131
|
top: number;
|
|
131
132
|
parked: Pt[];
|
|
132
133
|
};
|
|
134
|
+
/** 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. */
|
|
135
|
+
dome?: {
|
|
136
|
+
rim: Pt[];
|
|
137
|
+
low: number;
|
|
138
|
+
crown: number;
|
|
139
|
+
};
|
|
140
|
+
/** Draw the walls as ivy-covered (a plain drawing choice: the wall in green). */
|
|
141
|
+
ivy?: boolean;
|
|
133
142
|
/** 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. */
|
|
134
143
|
crown?: {
|
|
135
144
|
rise: number;
|
package/dist/crowd.draw.js
CHANGED
|
@@ -135,7 +135,7 @@ const poly = (points) => `M${points.map(([x, y]) => `${Math.round(x)} ${Math.rou
|
|
|
135
135
|
/** The building of a real venue and, where its rink's orientation is known, a small arrow with an N that says where north lies. The N is a path of three strokes, so that no font is needed. */
|
|
136
136
|
export function realMarkup(real) {
|
|
137
137
|
const fp = real.footprint;
|
|
138
|
-
let svg = el("path", { class: "ky-cr-shell", d: `M${fp.map(([x, y]) => `${n1(x)} ${n1(y)}`).join("L")}Z` });
|
|
138
|
+
let svg = el("path", { class: real.ivy === true ? "ky-cr-shell ky-cr-ivy" : "ky-cr-shell", d: `M${fp.map(([x, y]) => `${n1(x)} ${n1(y)}`).join("L")}Z` });
|
|
139
139
|
if (real.north !== undefined) {
|
|
140
140
|
const top = Math.min(...fp.map((p) => p[1]));
|
|
141
141
|
const right = Math.max(...fp.map((p) => p[0]));
|
|
@@ -250,6 +250,7 @@ export const KYOGIJO_CROWD_STYLE = `
|
|
|
250
250
|
.kyogijo .ky-cr-media{fill:#1c2127;fill-opacity:.4}
|
|
251
251
|
.kyogijo .ky-cr-camera{fill:#1c2127;fill-opacity:.75}
|
|
252
252
|
.kyogijo .ky-cr-shell{fill:var(--ky-cr-conc);fill-opacity:.55;stroke:var(--ky-cr-rim);stroke-width:1.6;stroke-linejoin:round}
|
|
253
|
+
.kyogijo .ky-cr-ivy{fill:#5b8a45;fill-opacity:.6}
|
|
253
254
|
.kyogijo .ky-cr-north{fill:var(--ky-cr-rim);stroke:var(--ky-cr-rim);stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
|
|
254
255
|
.kyogijo .ky-cr-north-n{fill:none;stroke:var(--ky-cr-rim);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
|
|
255
256
|
.kyogijo .ky-cr-plaza{fill:var(--ky-apron);stroke:var(--ky-apron);stroke-width:8;stroke-linejoin:round}
|
package/dist/district.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Language, SurfaceOptions } from "./types.ts";
|
|
|
4
4
|
/** How a district is fitted: `neighbours` tight round venues that stand close together, `city` at the real distances between venues that are kilometres apart. */
|
|
5
5
|
export type DistrictMode = "neighbours" | "city";
|
|
6
6
|
export declare const DISTRICT_MODES: readonly DistrictMode[];
|
|
7
|
-
/** A venue of a district: its key, and for a stadium the configuration it is drawn in (`cfl` or `soccer`; the first it has where none is given). `"bc-place:soccer"` is the same as `{ id: "bc-place", config: "soccer" }`. */
|
|
7
|
+
/** A venue of a district: its key, and for a stadium the configuration it is drawn in (`cfl` or `soccer`; the first it has where none is given; a ballpark's is `mlb` and a hall's `boxing`). `"bc-place:soccer"` is the same as `{ id: "bc-place", config: "soccer" }`. */
|
|
8
8
|
export type DistrictVenueInput = string | {
|
|
9
9
|
id: string;
|
|
10
10
|
config?: StadiumConfigId;
|
|
@@ -33,7 +33,7 @@ export interface DistrictMember {
|
|
|
33
33
|
/** The key of the venue and the configuration, unique in a district: `rogers-arena`, `bc-place:soccer`. */
|
|
34
34
|
key: string;
|
|
35
35
|
id: string;
|
|
36
|
-
kind: "arena" | "stadium" | "park";
|
|
36
|
+
kind: "arena" | "stadium" | "park" | "hall";
|
|
37
37
|
config?: StadiumConfigId;
|
|
38
38
|
/** The name in both languages. */
|
|
39
39
|
name: {
|
package/dist/district.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// A district: several real venues of one city placed on one plan at their real relative positions and orientations, read from the same OpenStreetMap outlines each venue is drawn from (`venues.ts`, `stadiums.ts`).
|
|
2
|
-
// It is generic: a district is a list of venue keys, whichever kinds they are (an NHL arena, a football or soccer stadium, a ballpark
|
|
2
|
+
// It is generic: a district is a list of venue keys, whichever kinds they are (an NHL arena, a football or soccer stadium, a ballpark, a hall with a boxing ring), and the plan fits itself to them. Two modes say how far apart the venues are:
|
|
3
3
|
// `neighbours` fits the plan tight round buildings that stand within a few hundred metres of each other, with no more than a margin between them; `city` keeps the real distances between venues that are kilometres
|
|
4
4
|
// apart, with a grid, a scale and a name beside each. Nothing is drawn but what the venues are drawn from: no street, no park and no mark of a team, a league or a tournament; the credit
|
|
5
5
|
// `VENUE_ATTRIBUTION` goes with it. Pure: options in, a plan, and markup, out; `district.turn.ts` raises it in 3D.
|
|
@@ -34,7 +34,7 @@ const stadiumMember = (stadium, config, language) => {
|
|
|
34
34
|
return {
|
|
35
35
|
key: `${stadium.id}:${config}`,
|
|
36
36
|
id: stadium.id,
|
|
37
|
-
kind: park ? "park" : "stadium",
|
|
37
|
+
kind: park ? "park" : stadiumKind(stadium) === "hall" ? "hall" : "stadium",
|
|
38
38
|
config,
|
|
39
39
|
name: stadiumNames(stadium),
|
|
40
40
|
ring: stadium.osm.ring,
|
package/dist/draw.js
CHANGED
|
@@ -324,7 +324,9 @@ function buildWhole(o, portrait, id, score, scored, extra) {
|
|
|
324
324
|
const diamond = diamondOf(league, o.year);
|
|
325
325
|
return baseball(fenceFor(parkById(o.park), feetFence(o.fence, unit), o.year, diamondFence(diamond)), { ...common, ballpark: ballparkFor(o) }, undefined, diamond);
|
|
326
326
|
}
|
|
327
|
-
|
|
327
|
+
// A real ballpark whose infield is all dirt (Koshien Stadium's) has no grass inside the bases.
|
|
328
|
+
const dirt = realStadiumOf(o, "mlb", resolveCrowd(o.crowd)?.real === true)?.stadium.looks?.dirtInfield !== undefined;
|
|
329
|
+
return baseball(fenceFor(parkById(o.park), feetFence(o.fence, unit), o.year), { ...common, ballpark: ballparkFor(o), ...(dirt ? { dirtInfield: true } : {}) }, rulesFor("mlb", o.year).rules);
|
|
328
330
|
}
|
|
329
331
|
case "soccer":
|
|
330
332
|
return buildSoccer(o, common, scored, unit);
|
|
@@ -749,8 +751,8 @@ export function describe(o) {
|
|
|
749
751
|
const venue = sport === "nhl" && league === undefined && realCrowd?.real === true ? venueById(o.arena) : undefined;
|
|
750
752
|
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"]}`;
|
|
751
753
|
const real = realStadiumOf(o, sport, realCrowd?.real === true);
|
|
752
|
-
const stadiumKey = (basis) =>
|
|
753
|
-
const stadiumLine = real === undefined ? "" : `${gap}${fill(real.config === "mlb" ? words.said.stadiumPark : 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)]}`;
|
|
754
|
+
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";
|
|
755
|
+
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))]}`;
|
|
754
756
|
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}`);
|
|
755
757
|
// A drawing of a past season says which: the markings are those of that season.
|
|
756
758
|
const dated = typeof o.year === "number" && Number.isFinite(o.year);
|
package/dist/stadiums.d.ts
CHANGED
|
@@ -5,15 +5,16 @@ 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. */
|
|
9
|
-
export type StadiumConfigId = "cfl" | "nfl" | "soccer" | "mlb";
|
|
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. */
|
|
11
|
-
export type StadiumKind = "stadium" | "park";
|
|
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";
|
|
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
|
+
export type StadiumKind = "stadium" | "park" | "hall";
|
|
12
12
|
/**
|
|
13
13
|
* The kinds of roof: `cable` is a cable-supported roof with an opening over the field that a retractable fabric closes (BC Place's); `fixed` a lid over the bowl; `canopy` roofs over some of the stands that leave the field
|
|
14
|
-
* open (Lumen Field's); `panels` a retractable roof of sections high over a ballpark (T-Mobile Park's)
|
|
14
|
+
* open (Lumen Field's); `panels` a retractable roof of sections high over a ballpark (T-Mobile Park's); `dome` an air-supported roof, a membrane held up by the air inside (Tokyo Dome's); `open` no roof over the
|
|
15
|
+
* field (Koshien Stadium's).
|
|
15
16
|
*/
|
|
16
|
-
export type StadiumRoof = "cable" | "fixed" | "canopy" | "panels";
|
|
17
|
+
export type StadiumRoof = "cable" | "fixed" | "canopy" | "panels" | "dome" | "open";
|
|
17
18
|
/** What one configuration of a stadium is: its sport and its seats, and where the figure was read. */
|
|
18
19
|
export interface StadiumConfigData {
|
|
19
20
|
sport: StadiumConfigId;
|
|
@@ -41,6 +42,25 @@ export interface RealStadiumData {
|
|
|
41
42
|
title: string;
|
|
42
43
|
revision: number;
|
|
43
44
|
};
|
|
45
|
+
/** The Japanese Wikipedia article some of the facts were read from, at a revision. */
|
|
46
|
+
ja?: {
|
|
47
|
+
title: string;
|
|
48
|
+
revision: number;
|
|
49
|
+
};
|
|
50
|
+
/** The names of a hall, in both languages (a stadium's and a park's are those of its ground and its park): Wikidata's labels. */
|
|
51
|
+
names?: {
|
|
52
|
+
en: string;
|
|
53
|
+
ja: string;
|
|
54
|
+
readingJa?: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Plain drawing choices where the sources say so, each with what was read: `ivyWalls` draws the building's walls in green (Koshien Stadium's ivy), `dirtInfield` draws the infield all dirt, with no grass inside the bases
|
|
58
|
+
* (the same stadium's). They are drawn as plain colours, not as a picture of the plants or the soil.
|
|
59
|
+
*/
|
|
60
|
+
looks?: {
|
|
61
|
+
ivyWalls?: string;
|
|
62
|
+
dirtInfield?: string;
|
|
63
|
+
};
|
|
44
64
|
configs: Partial<Record<StadiumConfigId, StadiumConfigData>>;
|
|
45
65
|
/** 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. */
|
|
46
66
|
bowlSeats?: number;
|
|
@@ -72,7 +92,7 @@ export interface RealStadiumData {
|
|
|
72
92
|
};
|
|
73
93
|
/** A canopy's share of the seats it covers, as the article gives it. */
|
|
74
94
|
cover?: number;
|
|
75
|
-
/** A retractable roof's bottom and top above the field, in feet, as the article gives them. */
|
|
95
|
+
/** 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). */
|
|
76
96
|
bottom?: number;
|
|
77
97
|
top?: number;
|
|
78
98
|
};
|
|
@@ -97,6 +117,7 @@ export interface StadiumOsm {
|
|
|
97
117
|
source: string;
|
|
98
118
|
ring: [number, number][];
|
|
99
119
|
};
|
|
120
|
+
ring?: [number, number][];
|
|
100
121
|
};
|
|
101
122
|
/** The canopies OpenStreetMap maps over some of the stands: each one's outline, the height of its top and how far it rises (metres). */
|
|
102
123
|
canopies?: {
|
|
@@ -109,9 +130,10 @@ export interface StadiumOsm {
|
|
|
109
130
|
ring: [number, number][];
|
|
110
131
|
/**
|
|
111
132
|
* 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
|
|
112
|
-
* is known (`opening`: from the roof's opening, which the article says is the field's size; `pitch`: from the pitches OpenStreetMap maps
|
|
113
|
-
*
|
|
114
|
-
*
|
|
133
|
+
* 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
|
|
134
|
+
* 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
|
|
135
|
+
* 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)),
|
|
136
|
+
* 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).
|
|
115
137
|
*/
|
|
116
138
|
axis: {
|
|
117
139
|
heading: number;
|
|
@@ -119,6 +141,7 @@ export interface StadiumOsm {
|
|
|
119
141
|
strength: number;
|
|
120
142
|
elongation: number;
|
|
121
143
|
centre: [number, number];
|
|
144
|
+
rms?: number;
|
|
122
145
|
};
|
|
123
146
|
}
|
|
124
147
|
/** A stadium: its facts and what OpenStreetMap gave. */
|
|
@@ -143,9 +166,9 @@ export declare const stadiumByWikidata: (wikidata: string | undefined) => RealSt
|
|
|
143
166
|
export declare const stadiumKind: (stadium: RealStadium) => StadiumKind;
|
|
144
167
|
/** The configurations a stadium has, in the order of its data. */
|
|
145
168
|
export declare const stadiumConfigs: (stadium: RealStadium) => StadiumConfigId[];
|
|
146
|
-
/** The ground a stadium is (its name, place and pitch). Throws for a ballpark, which has a park. */
|
|
169
|
+
/** The ground a stadium is (its name, place and pitch). Throws for a ballpark, which has a park, and for a hall, which has none. */
|
|
147
170
|
export declare const groundOfStadium: (stadium: RealStadium) => Ground;
|
|
148
|
-
/** The names a stadium goes by in both languages, of its ground or its park. */
|
|
171
|
+
/** The names a stadium goes by in both languages, of its ground or its park (a hall's own). */
|
|
149
172
|
export declare function stadiumNames(stadium: RealStadium): {
|
|
150
173
|
en: string;
|
|
151
174
|
ja: string;
|
|
@@ -154,10 +177,12 @@ export declare function stadiumNames(stadium: RealStadium): {
|
|
|
154
177
|
export declare const stadiumFrameHeading: (stadium: RealStadium) => number;
|
|
155
178
|
/** How the field lies in a stadium's building (see `StadiumOsm.axis`). */
|
|
156
179
|
export declare const stadiumOrientation: (stadium: RealStadium) => StadiumOrientation;
|
|
157
|
-
/** The configuration a sport is: `cfl`, `nfl`, `soccer` or `
|
|
180
|
+
/** The configuration a sport is: `cfl`, `nfl`, `soccer`, `mlb` or `boxing`, or `undefined` for a sport a stadium has no configuration for. */
|
|
158
181
|
export declare const stadiumConfigOf: (sport: string | undefined) => StadiumConfigId | undefined;
|
|
159
182
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
160
183
|
export declare const stadiumSeats: (stadium: RealStadium, config: StadiumConfigId) => number;
|
|
184
|
+
/** How far a hall's first row is from the box of the ring, on every side, in feet (ours: the seats at the ringside are a few feet from the ropes). */
|
|
185
|
+
export declare const HALL_FIRST_ROW = 8;
|
|
161
186
|
/** The height of the first row above the field, in feet, the rise of a bowl's rows by the number of bowls (what the lowest, a middle and the upper rise, at most), and the rise of the walk between two bowls (a suite level): ours, no source gives them. */
|
|
162
187
|
export declare const STADIUM_FIRST_HEIGHT = 5;
|
|
163
188
|
export declare const STADIUM_RISES: Readonly<Record<1 | 2 | 3, readonly number[]>>;
|
|
@@ -229,6 +254,8 @@ export interface ParkPlan {
|
|
|
229
254
|
seats: number;
|
|
230
255
|
}[];
|
|
231
256
|
total: number;
|
|
257
|
+
/** The floor a seat takes in the plan, in square feet: 4.6 as the usual stands have it, less where the building is too small for them and the bowl is steeper. */
|
|
258
|
+
seatArea: number;
|
|
232
259
|
/** Whether the stands hold the configuration's seats to within two percent. */
|
|
233
260
|
fit: "fits" | "tight";
|
|
234
261
|
}
|
package/dist/stadiums.data.js
CHANGED
|
@@ -33,4 +33,29 @@ export const STADIUM_DATA = [
|
|
|
33
33
|
"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" },
|
|
34
34
|
"roof": { "kind": "panels", "basis": "documented", "bottom": 217, "top": 269, "read": "the article: a retractable roof in four sections that does not fully enclose the ballpark, its bottom 217 feet and its top 269 feet above the field, resting over the railway tracks to the east when open" },
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
"id": "tokyo-dome",
|
|
38
|
+
"kind": "park",
|
|
39
|
+
"wikidata": "Q733748",
|
|
40
|
+
"park": "tokyo-dome",
|
|
41
|
+
"en": { "title": "Tokyo Dome", "revision": 1379232526 },
|
|
42
|
+
"ja": { "title": "東京ドーム", "revision": 111434287 },
|
|
43
|
+
"configs": { "mlb": { "sport": "mlb", "seats": 45600, "source": "wikipedia", "read": "the infobox's capacity for baseball, 45,600 (42,000 to 55,000 for events, 57,000 at the most)" } },
|
|
44
|
+
"wikidataSeats": [55000],
|
|
45
|
+
"tiers": { "count": 2, "basis": "documented", "read": "the Japanese article: the infield stands are in two levels with a balcony level between them, and the outfield stands are on one level" },
|
|
46
|
+
"roof": { "kind": "dome", "basis": "documented", "read": "the articles: an air-supported roof, a double membrane of coated glass cloth on a net of 28 wires 8.5 metres apart, held up by blowing the air inside 0.3 percent over the outside; 56 metres high inside" },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "korakuen-hall",
|
|
50
|
+
"kind": "hall",
|
|
51
|
+
"wikidata": "Q4389857",
|
|
52
|
+
"names": { "en": "Korakuen Hall", "ja": "後楽園ホール", "readingJa": "こうらくえんホール" },
|
|
53
|
+
"en": { "title": "Korakuen Hall", "revision": 1372211318 },
|
|
54
|
+
"ja": { "title": "後楽園ホール", "revision": 111205284 },
|
|
55
|
+
"configs": { "boxing": { "sport": "boxing", "seats": 1403, "source": "wikipedia", "read": "the infobox's capacity, 1,403 seats (2,005 at the most, with the balconies used for standing)" } },
|
|
56
|
+
"bowlSeats": 2005,
|
|
57
|
+
"wikidataSeats": [2005],
|
|
58
|
+
"tiers": { "count": 2, "basis": "documented", "lowerSeats": 1403, "read": "the Japanese article: the 1,403 seats are on the floor (780 fixed seats on the south side, movable seats of 109 east, 109 west and 405 north), and the balconies on the east and west sides, above, take standing places up to 2,005" },
|
|
59
|
+
"roof": { "kind": "fixed", "basis": "plausible", "read": "the hall is on the fifth floor of a building (the Japanese article), under the building's own roof, drawn as a lid (ours)" },
|
|
60
|
+
},
|
|
36
61
|
];
|
package/dist/stadiums.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The real stadiums: for a stadium that stages football and soccer, or a
|
|
1
|
+
// The real stadiums: for a stadium that stages football and soccer, a ballpark, or a hall that stages boxing, the outline of its building and the way its field lies in it (from OpenStreetMap, ODbL 1.0), and a bowl of the seats of each configuration,
|
|
2
2
|
// the number of bowls and the kind of roof the sources give (Wikidata, CC0, and the English Wikipedia article each fact cites), fitted inside the outline. It is the stadium's counterpart of `venues.ts`, for a surface that is a
|
|
3
3
|
// field and not a rink: level one is the building, level two a plausible bowl, and it stops there (no team's, ticket seller's or federation's seat map is read or copied, no section or row is named, and no mark of a club, a
|
|
4
4
|
// league or a tournament is drawn). The drawing is `drawKyogijo({ sport: "cfl", stadium, crowd: { real: true } })` (or `sport: "soccer"` with a `ground` or a `stadium`, `sport: "mlb"` with a `park`) and, in 3D, the same options
|
|
@@ -26,15 +26,20 @@ export const stadiumByWikidata = (wikidata) => Object.values(STADIUMS).find((s)
|
|
|
26
26
|
export const stadiumKind = (stadium) => stadium.kind ?? "stadium";
|
|
27
27
|
/** The configurations a stadium has, in the order of its data. */
|
|
28
28
|
export const stadiumConfigs = (stadium) => Object.keys(stadium.configs);
|
|
29
|
-
/** The ground a stadium is (its name, place and pitch). Throws for a ballpark, which has a park. */
|
|
29
|
+
/** The ground a stadium is (its name, place and pitch). Throws for a ballpark, which has a park, and for a hall, which has none. */
|
|
30
30
|
export const groundOfStadium = (stadium) => {
|
|
31
31
|
const ground = groundById(stadium.ground);
|
|
32
32
|
if (ground === undefined)
|
|
33
33
|
throw new Error(`${stadium.id} has no ground ${String(stadium.ground)}`);
|
|
34
34
|
return ground;
|
|
35
35
|
};
|
|
36
|
-
/** The names a stadium goes by in both languages, of its ground or its park. */
|
|
36
|
+
/** The names a stadium goes by in both languages, of its ground or its park (a hall's own). */
|
|
37
37
|
export function stadiumNames(stadium) {
|
|
38
|
+
if (stadiumKind(stadium) === "hall") {
|
|
39
|
+
if (stadium.names === undefined)
|
|
40
|
+
throw new Error(`${stadium.id} is a hall with no names`);
|
|
41
|
+
return { en: stadium.names.en, ja: stadium.names.ja };
|
|
42
|
+
}
|
|
38
43
|
if (stadiumKind(stadium) === "park") {
|
|
39
44
|
const park = PARKS[stadium.park ?? ""];
|
|
40
45
|
if (park === undefined)
|
|
@@ -50,13 +55,15 @@ const lonLat = (ring) => ring.map(([lon, lat]) => [lon, lat]);
|
|
|
50
55
|
export const stadiumFrameHeading = (stadium) => (stadiumKind(stadium) === "park" ? (stadium.osm.axis.heading + 90) % 360 : stadium.osm.axis.heading);
|
|
51
56
|
/** How the field lies in a stadium's building (see `StadiumOsm.axis`). */
|
|
52
57
|
export const stadiumOrientation = (stadium) => ({ heading: stadium.osm.axis.heading, basis: stadium.osm.axis.basis, north: round(northOn(stadiumFrameHeading(stadium))) });
|
|
53
|
-
/** The configuration a sport is: `cfl`, `nfl`, `soccer` or `
|
|
54
|
-
export const stadiumConfigOf = (sport) => (sport === "cfl" || sport === "nfl" || sport === "soccer" || sport === "mlb" ? sport : undefined);
|
|
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);
|
|
55
60
|
/** The seats of a stadium for a configuration (0 for one it has none of). */
|
|
56
61
|
export const stadiumSeats = (stadium, config) => stadium.configs[config]?.seats ?? 0;
|
|
57
62
|
// ---- The bowl. ----
|
|
58
63
|
/** How far the first row is from a football field's footprint (the league's field and its end zones) along a side and at an end, and the corner's radius: the package's own gridiron profile (`crowd.ts`), so that a real stadium's first row is the usual one. */
|
|
59
64
|
const FIRST_ROW = { cfl: { side: 42, end: 28, corner: 60 }, nfl: { side: 40, end: 26, corner: 60 } };
|
|
65
|
+
/** How far a hall's first row is from the box of the ring, on every side, in feet (ours: the seats at the ringside are a few feet from the ropes). */
|
|
66
|
+
export const HALL_FIRST_ROW = 8;
|
|
60
67
|
/** The height of the first row above the field, in feet, the rise of a bowl's rows by the number of bowls (what the lowest, a middle and the upper rise, at most), and the rise of the walk between two bowls (a suite level): ours, no source gives them. */
|
|
61
68
|
export const STADIUM_FIRST_HEIGHT = 5;
|
|
62
69
|
export const STADIUM_RISES = { 1: [60], 2: [38, 62], 3: [32, 26, 56] };
|
|
@@ -102,7 +109,7 @@ const PLANS = new WeakMap();
|
|
|
102
109
|
* ```
|
|
103
110
|
*/
|
|
104
111
|
export function planStadium(stadium, config, box) {
|
|
105
|
-
if (stadiumKind(stadium)
|
|
112
|
+
if (stadiumKind(stadium) === "park")
|
|
106
113
|
throw new Error(`${stadium.id} is a ballpark: planPark plans it`);
|
|
107
114
|
if (stadium.configs[config] === undefined)
|
|
108
115
|
throw new Error(`${stadium.id} has no configuration ${config}`);
|
|
@@ -133,7 +140,10 @@ function plan(stadium, config, rink) {
|
|
|
133
140
|
const gridiron = FOOTBALL[kind];
|
|
134
141
|
const row = FIRST_ROW[kind];
|
|
135
142
|
const length = gridiron.length + 2 * gridiron.endZone;
|
|
136
|
-
|
|
143
|
+
// A hall's first row stands a little way from the ring's box on every side; a stadium's round a football field's footprint.
|
|
144
|
+
const first = stadiumKind(stadium) === "hall"
|
|
145
|
+
? { kind: "rrect", x0: rink.x - HALL_FIRST_ROW, y0: rink.y - HALL_FIRST_ROW, x1: rink.x + rink.w + HALL_FIRST_ROW, y1: rink.y + rink.h + HALL_FIRST_ROW, r: HALL_FIRST_ROW }
|
|
146
|
+
: { kind: "rrect", x0: at[0] - length / 2 - row.end, y0: at[1] - gridiron.width / 2 - row.side, x1: at[0] + length / 2 + row.end, y1: at[1] + gridiron.width / 2 + row.side, r: row.corner };
|
|
137
147
|
const shapedAs = (long, s) => ({ kind: "rrect", x0: at[0] - long * s, y0: at[1] - s, x1: at[0] + long * s, y1: at[1] + s, r: CORNER * s });
|
|
138
148
|
const largest = (long) => {
|
|
139
149
|
let [lo, hi] = [60, Math.max(fb.x1 - fb.x0, fb.y1 - fb.y0)];
|
|
@@ -216,6 +226,14 @@ function plan(stadium, config, rink) {
|
|
|
216
226
|
const missing = seatsTotal - rings.reduce((sum, ring) => sum + ring.seats, 0);
|
|
217
227
|
if (!tight && Math.abs(missing) <= 3)
|
|
218
228
|
rings[rings.length - 1].seats += missing;
|
|
229
|
+
// Where a source gives the seats of the lowest bowl, that bowl holds them to the seat (the rounding of its area leaves it one or two off) and the last bowl the rest.
|
|
230
|
+
if (!tight && stadium.tiers.lowerSeats !== undefined && rings.length > 1) {
|
|
231
|
+
const off = stadium.tiers.lowerSeats - rings[0].seats;
|
|
232
|
+
if (off !== 0 && Math.abs(off) <= 3) {
|
|
233
|
+
rings[0].seats += off;
|
|
234
|
+
rings[rings.length - 1].seats -= off;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
219
237
|
const total = rings.reduce((sum, ring) => sum + ring.seats, 0);
|
|
220
238
|
// A configuration that opens fewer seats than the bowl holds opens them from the lowest bowl up: a bowl's people are its open share of them, and a closed one is empty.
|
|
221
239
|
if (open < seatsTotal) {
|
|
@@ -260,6 +278,8 @@ export function stadiumBowlInside(plan) {
|
|
|
260
278
|
const PARK_PLANS = new WeakMap();
|
|
261
279
|
/** The share of a ballpark's building the closed roof covers, and the share of its width along the east side the open roof's sections rest over (ours: the article says where they rest, not how much). */
|
|
262
280
|
const PARK_COVER = 0.92;
|
|
281
|
+
/** How far an open-air ballpark's walls stand over its top row, in feet (ours). */
|
|
282
|
+
const OPEN_WALL_OVER = 6;
|
|
263
283
|
const PARK_PARKED = 0.3;
|
|
264
284
|
/**
|
|
265
285
|
* 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
|
|
@@ -284,17 +304,6 @@ function park(stadium, input) {
|
|
|
284
304
|
const bands = stadium.tiers.tierSeats ?? { lower: seats * 0.45, upper: seats * 0.47, bleachers: seats * 0.08 };
|
|
285
305
|
const sum = bands.lower + bands.upper + bands.bleachers;
|
|
286
306
|
const wanted = { lower: (bands.lower / sum) * seats, upper: (bands.upper / sum) * seats, bleachers: (bands.bleachers / sum) * seats };
|
|
287
|
-
// 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).
|
|
288
|
-
const depths = { lower: 60, upper: 56, bleachers: 54 };
|
|
289
|
-
let stands = parkLayout({ ...input, depths });
|
|
290
|
-
for (let i = 0; i < 12; i += 1) {
|
|
291
|
-
const held = (id) => stands.rings.find((r) => r.id === id)?.seats ?? 0;
|
|
292
|
-
const next = { lower: depths.lower * (wanted.lower / Math.max(1, held("lower"))), upper: depths.upper * (wanted.upper / Math.max(1, held("upper"))), bleachers: depths.bleachers * (wanted.bleachers / Math.max(1, held("bleachers"))) };
|
|
293
|
-
for (const k of ["lower", "upper", "bleachers"])
|
|
294
|
-
depths[k] = Math.min(160, Math.max(20, next[k]));
|
|
295
|
-
stands = parkLayout({ ...input, depths });
|
|
296
|
-
}
|
|
297
|
-
const total = stands.rings.reduce((n, r) => n + r.seats, 0);
|
|
298
307
|
const orientation = stadiumOrientation(stadium);
|
|
299
308
|
const frameHeading = stadiumFrameHeading(stadium);
|
|
300
309
|
// The building in the park's frame, in feet: home plate at the origin.
|
|
@@ -307,27 +316,63 @@ function park(stadium, input) {
|
|
|
307
316
|
};
|
|
308
317
|
const footprint = world.map(frameOf);
|
|
309
318
|
const fb = boxOf(footprint);
|
|
319
|
+
// 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
|
|
320
|
+
// 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.
|
|
321
|
+
let depths = { lower: 60, upper: 56, bleachers: 54 };
|
|
322
|
+
let stands = parkLayout({ ...input, depths });
|
|
323
|
+
let factor = 1;
|
|
324
|
+
for (const f of [1, ...SEAT_AREAS.map((area) => 4.6 / area)]) {
|
|
325
|
+
factor = f;
|
|
326
|
+
depths = { lower: 60, upper: 56, bleachers: 54 };
|
|
327
|
+
stands = parkLayout({ ...input, depths });
|
|
328
|
+
for (let i = 0; i < 12; i += 1) {
|
|
329
|
+
const held = (id) => (stands.rings.find((r) => r.id === id)?.seats ?? 0) * f;
|
|
330
|
+
const next = { lower: depths.lower * (wanted.lower / Math.max(1, held("lower"))), upper: depths.upper * (wanted.upper / Math.max(1, held("upper"))), bleachers: depths.bleachers * (wanted.bleachers / Math.max(1, held("bleachers"))) };
|
|
331
|
+
for (const k of ["lower", "upper", "bleachers"])
|
|
332
|
+
depths[k] = Math.min(160, Math.max(20, next[k]));
|
|
333
|
+
stands = parkLayout({ ...input, depths });
|
|
334
|
+
}
|
|
335
|
+
if (stands.rings.every((r) => [r.region, ...(r.more ?? [])].every((o) => o.kind !== "poly" || o.points.every((p) => inside(footprint, p)))))
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (factor !== 1) {
|
|
339
|
+
for (const ring of stands.rings)
|
|
340
|
+
ring.seats = Math.round(ring.seats * factor);
|
|
341
|
+
stands.seats = stands.rings.reduce((n, r) => n + r.seats, 0);
|
|
342
|
+
}
|
|
343
|
+
const total = stands.rings.reduce((n, r) => n + r.seats, 0);
|
|
310
344
|
const box = stands.box;
|
|
311
345
|
const c = [box.x + box.w / 2, box.y + box.h / 2];
|
|
312
|
-
const cover = footprint.map((p) => [c[0] + (p[0] - c[0]) * PARK_COVER, c[1] + (p[1] - c[1]) * PARK_COVER]);
|
|
313
|
-
// Where the sections rest: the east side of the building, over the railway there (the article says so): the strip of the outline east of a share of its width.
|
|
314
|
-
const [wx0, wx1] = [Math.min(...world.map((p) => p[0])), Math.max(...world.map((p) => p[0]))];
|
|
315
|
-
const [wy0, wy1] = [Math.min(...world.map((p) => p[1])), Math.max(...world.map((p) => p[1]))];
|
|
316
|
-
const strip = clipConvex(world, [[wx1 - (wx1 - wx0) * PARK_PARKED, wy0 - 1], [wx1 + 1, wy0 - 1], [wx1 + 1, wy1 + 1], [wx1 - (wx1 - wx0) * PARK_PARKED, wy1 + 1]]);
|
|
317
|
-
const parked = strip.map(frameOf);
|
|
318
346
|
const north = orientation.basis === "none" ? undefined : orientation.north;
|
|
319
347
|
const room = north === undefined ? 0 : NORTH_ROOM;
|
|
320
|
-
const wallFeet = round((stadium.osm.height ?? 20) * FEET_PER_METRE);
|
|
321
348
|
const hall = { kind: "rrect", x0: fb.x0 - 30, y0: fb.y0 - 30, x1: fb.x1 + 30, y1: fb.y1 + 30, r: 0 };
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
roof:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
349
|
+
const top = Math.max(...stands.rings.map((r) => r.height[1]));
|
|
350
|
+
const ivy = stadium.looks?.ivyWalls === undefined ? {} : { ivy: true };
|
|
351
|
+
let shell;
|
|
352
|
+
if (stadium.roof.kind === "dome") {
|
|
353
|
+
// 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.
|
|
354
|
+
const mapped = stadium.osm.roof;
|
|
355
|
+
if (mapped?.ring === undefined || mapped.height === undefined || mapped.rise === undefined)
|
|
356
|
+
throw new Error(`${stadium.id} has a dome roof and no mapped roof`);
|
|
357
|
+
const rim = metresFrom(origin, lonLat(mapped.ring)).map(frameOf);
|
|
358
|
+
const low = round((mapped.height - mapped.rise) * FEET_PER_METRE);
|
|
359
|
+
shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "dome", edge: low, wall: low, dome: { rim, low, crown: round(mapped.height * FEET_PER_METRE) }, ...ivy };
|
|
360
|
+
}
|
|
361
|
+
else if (stadium.roof.kind === "open") {
|
|
362
|
+
// No roof: the walls stand a little over the top row.
|
|
363
|
+
const wall = round(Math.max(top + OPEN_WALL_OVER, (stadium.osm.height ?? 0) * FEET_PER_METRE));
|
|
364
|
+
shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "open", edge: wall, wall, ...ivy };
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const cover = footprint.map((p) => [c[0] + (p[0] - c[0]) * PARK_COVER, c[1] + (p[1] - c[1]) * PARK_COVER]);
|
|
368
|
+
// Where the sections rest: the east side of the building, over the railway there (the article says so): the strip of the outline east of a share of its width.
|
|
369
|
+
const [wx0, wx1] = [Math.min(...world.map((p) => p[0])), Math.max(...world.map((p) => p[0]))];
|
|
370
|
+
const [wy0, wy1] = [Math.min(...world.map((p) => p[1])), Math.max(...world.map((p) => p[1]))];
|
|
371
|
+
const strip = clipConvex(world, [[wx1 - (wx1 - wx0) * PARK_PARKED, wy0 - 1], [wx1 + 1, wy0 - 1], [wx1 + 1, wy1 + 1], [wx1 - (wx1 - wx0) * PARK_PARKED, wy1 + 1]]);
|
|
372
|
+
const parked = strip.map(frameOf);
|
|
373
|
+
const wallFeet = round((stadium.osm.height ?? 20) * FEET_PER_METRE);
|
|
374
|
+
shell = { footprint, hall, ...(north === undefined ? {} : { north }), roof: "panels", edge: wallFeet, wall: wallFeet, panels: { cover, bottom: stadium.roof.bottom ?? 217, top: stadium.roof.top ?? 269, parked }, ...ivy };
|
|
375
|
+
}
|
|
331
376
|
const reach = {
|
|
332
377
|
left: Math.max(stands.reach.left, box.x - fb.x0),
|
|
333
378
|
right: Math.max(stands.reach.right, fb.x1 - box.x - box.w),
|
|
@@ -335,5 +380,5 @@ function park(stadium, input) {
|
|
|
335
380
|
bottom: Math.max(stands.reach.bottom, fb.y1 - box.y - box.h),
|
|
336
381
|
};
|
|
337
382
|
const real = { ...stands, real: shell, reach };
|
|
338
|
-
return { stadium, config, orientation, footprint, stands: real, depths: { lower: round(depths.lower), upper: round(depths.upper), bleachers: round(depths.bleachers) }, seats: stands.rings.map((r) => ({ tier: r.id, seats: r.seats })), total, fit: Math.abs(total - seats) / seats <= 0.02 ? "fits" : "tight" };
|
|
383
|
+
return { stadium, config, orientation, footprint, stands: real, depths: { lower: round(depths.lower), upper: round(depths.upper), bleachers: round(depths.bleachers) }, seats: stands.rings.map((r) => ({ tier: r.id, seats: r.seats })), total, seatArea: round(4.6 / factor, 1), fit: Math.abs(total - seats) / seats <= 0.02 ? "fits" : "tight" };
|
|
339
384
|
}
|
|
@@ -169,4 +169,104 @@ export const STADIUM_OSM = {
|
|
|
169
169
|
[-122.333251, 47.592271],
|
|
170
170
|
],
|
|
171
171
|
},
|
|
172
|
+
"tokyo-dome": {
|
|
173
|
+
"source": "way/20028328",
|
|
174
|
+
"read": "2026-10-20",
|
|
175
|
+
"base": "2026-10-21T00:17:36Z",
|
|
176
|
+
"licence": "ODbL-1.0",
|
|
177
|
+
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
178
|
+
"axis": { "heading": 55, "basis": "none", "strength": 1.2, "elongation": 1.13, "centre": [139.751525, 35.705268] },
|
|
179
|
+
"roof": {
|
|
180
|
+
"source": "way/577764996",
|
|
181
|
+
"height": 56.19,
|
|
182
|
+
"rise": 30,
|
|
183
|
+
"ring": [
|
|
184
|
+
[139.752871, 35.706144],
|
|
185
|
+
[139.752932, 35.706031],
|
|
186
|
+
[139.752976, 35.705847],
|
|
187
|
+
[139.752975, 35.705675],
|
|
188
|
+
[139.752921, 35.705283],
|
|
189
|
+
[139.75286, 35.705117],
|
|
190
|
+
[139.752782, 35.704983],
|
|
191
|
+
[139.75269, 35.70489],
|
|
192
|
+
[139.752569, 35.70481],
|
|
193
|
+
[139.752398, 35.70474],
|
|
194
|
+
[139.75215, 35.704703],
|
|
195
|
+
[139.75182, 35.704713],
|
|
196
|
+
[139.75155, 35.704741],
|
|
197
|
+
[139.751333, 35.704784],
|
|
198
|
+
[139.75115, 35.704846],
|
|
199
|
+
[139.751022, 35.704923],
|
|
200
|
+
[139.750934, 35.705001],
|
|
201
|
+
[139.750846, 35.705134],
|
|
202
|
+
[139.750789, 35.70533],
|
|
203
|
+
[139.75078, 35.705468],
|
|
204
|
+
[139.750809, 35.705726],
|
|
205
|
+
[139.750861, 35.705993],
|
|
206
|
+
[139.750925, 35.706155],
|
|
207
|
+
[139.751, 35.70625],
|
|
208
|
+
[139.751107, 35.706344],
|
|
209
|
+
[139.751219, 35.706412],
|
|
210
|
+
[139.75136, 35.706461],
|
|
211
|
+
[139.751524, 35.70649],
|
|
212
|
+
[139.751747, 35.706495],
|
|
213
|
+
[139.752179, 35.706462],
|
|
214
|
+
[139.752393, 35.706426],
|
|
215
|
+
[139.752659, 35.706339],
|
|
216
|
+
[139.752775, 35.706248],
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
"ring": [
|
|
220
|
+
[139.75136, 35.706461],
|
|
221
|
+
[139.751524, 35.70649],
|
|
222
|
+
[139.751747, 35.706495],
|
|
223
|
+
[139.752179, 35.706462],
|
|
224
|
+
[139.752393, 35.706426],
|
|
225
|
+
[139.752659, 35.706339],
|
|
226
|
+
[139.752775, 35.706248],
|
|
227
|
+
[139.752871, 35.706144],
|
|
228
|
+
[139.752916, 35.706158],
|
|
229
|
+
[139.753075, 35.706065],
|
|
230
|
+
[139.753143, 35.705858],
|
|
231
|
+
[139.753151, 35.705673],
|
|
232
|
+
[139.753126, 35.705252],
|
|
233
|
+
[139.753079, 35.705055],
|
|
234
|
+
[139.752993, 35.704894],
|
|
235
|
+
[139.752866, 35.704753],
|
|
236
|
+
[139.752696, 35.704642],
|
|
237
|
+
[139.752467, 35.704559],
|
|
238
|
+
[139.752289, 35.704533],
|
|
239
|
+
[139.752261, 35.704562],
|
|
240
|
+
[139.751532, 35.704648],
|
|
241
|
+
[139.751445, 35.70461],
|
|
242
|
+
[139.751372, 35.704602],
|
|
243
|
+
[139.751286, 35.704665],
|
|
244
|
+
[139.751075, 35.704741],
|
|
245
|
+
[139.750998, 35.70489],
|
|
246
|
+
[139.751022, 35.704923],
|
|
247
|
+
[139.750934, 35.705001],
|
|
248
|
+
[139.750846, 35.705134],
|
|
249
|
+
[139.750789, 35.70533],
|
|
250
|
+
[139.75078, 35.705468],
|
|
251
|
+
[139.750861, 35.705993],
|
|
252
|
+
[139.750925, 35.706155],
|
|
253
|
+
[139.751, 35.70625],
|
|
254
|
+
[139.751107, 35.706344],
|
|
255
|
+
[139.751219, 35.706412],
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
"korakuen-hall": {
|
|
259
|
+
"source": "way/45977418",
|
|
260
|
+
"read": "2026-10-20",
|
|
261
|
+
"base": "2026-10-21T00:17:36Z",
|
|
262
|
+
"licence": "ODbL-1.0",
|
|
263
|
+
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
264
|
+
"axis": { "heading": 6, "basis": "building", "strength": 3.38, "elongation": 1.82, "centre": [139.752058, 35.704256] },
|
|
265
|
+
"ring": [
|
|
266
|
+
[139.751895, 35.704557],
|
|
267
|
+
[139.751831, 35.703995],
|
|
268
|
+
[139.752206, 35.703963],
|
|
269
|
+
[139.752282, 35.704525],
|
|
270
|
+
],
|
|
271
|
+
},
|
|
172
272
|
};
|
package/dist/strings.d.ts
CHANGED
|
@@ -520,6 +520,10 @@ export interface KyogijoWords {
|
|
|
520
520
|
stadiumPark: string;
|
|
521
521
|
stadiumPitch: string;
|
|
522
522
|
stadiumInfield: string;
|
|
523
|
+
stadiumHall: string;
|
|
524
|
+
stadiumRingBuilding: string;
|
|
525
|
+
stadiumPitchPark: string;
|
|
526
|
+
stadiumNonePark: string;
|
|
523
527
|
/** A district: a plan of {count} real venues at their real positions, {mode}; and what `mode` says for the two kinds of plan, and the word before the credit. */
|
|
524
528
|
district: string;
|
|
525
529
|
districtNeighbours: string;
|
package/dist/strings.js
CHANGED
|
@@ -199,7 +199,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
199
199
|
},
|
|
200
200
|
},
|
|
201
201
|
scoreTitle: "{away} {awayScore}, {home} {homeScore}",
|
|
202
|
-
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
202
|
+
said: { home: "home", away: "away", won: "{name} won.", tied: "The game is level.", park: "The outfield fence follows the figures published for {park}.", officially: "Its official name is {name}.", ground: "The pitch follows the size published for {ground}.", groundDefault: "No pitch size is published for {ground}, so the pitch has the international size of 105 by 68 metres.", arenaRink: "{arena}, hockey rink", arenaCourt: "{arena}, basketball court", arenaRinkAbout: "an ice hockey rink, {length} by {width} feet ({lengthM} by {widthM} metres) with rounded corners, with its red and blue lines, face-off circles, creases and goal nets", arenaCourtAbout: "a basketball court, {length} by {width} feet ({lengthM} by {widthM} metres), with its keys, three-point lines, free-throw circles and the centre circle", arenaSize: "The size is the one published for {arena}.", arenaUnknown: "The article says the rink of {arena} was shorter than the NHL's but gives no size, so the NHL's is drawn.", real: "The stands are a plausible bowl of {seats} seats in {tiers} tiers, fitted inside the outline of the building of {arena} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the arena's plan.", realBuilding: "The rink lies along the building's long axis, which is an inference, and the arrow points north.", realMapped: "The rink lies as OpenStreetMap maps the ice, and the arrow points north.", realNone: "The rink's orientation is not known, so it is drawn east-west with north at the top.", stadium: "The stands are a plausible bowl of {seats} seats in {tiers} bowls, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the stadium's plan.", stadiumOpening: "The field lies along the opening in the roof, which is the size of the field, and the arrow points north.", stadiumBuilding: "The field lies along the building's long axis, which is an inference, and the arrow points north.", stadiumNone: "The field's orientation is not known, so it is drawn east-west with north at the top.", stadiumHall: "The stands are a plausible bowl of {seats} seats in {tiers} levels, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the hall's plan.", stadiumRingBuilding: "The ring is set along the building's long axis, which is an inference, and the arrow points north.", stadiumPitchPark: "The way home plate faces is worked out from the grass of the field OpenStreetMap maps, fitted to the published fence, and the arrow points north.", stadiumNonePark: "The way home plate faces is not known, so the field is drawn the way that fits the building, and no arrow points north.", stadiumPark: "The stands are the usual stands of a ballpark, each band as deep as its seats need, for {seats} seats, fitted inside the outline of the building of {stadium} from OpenStreetMap ((c) OpenStreetMap contributors); it is not the park's plan.", stadiumPitch: "The field lies as OpenStreetMap maps the pitch, and the arrow points north.", stadiumInfield: "The way home plate faces is worked out from the infield OpenStreetMap maps and the building's outline, and the arrow points north.", district: "A plan of {count} real venues at their real positions and orientations, from the outlines OpenStreetMap maps; {mode}.", districtNeighbours: "it is fitted close round the buildings", districtCity: "it keeps the real distances, with a grid of kilometres and a scale", districtCredit: "Credit:", era: "The markings are those of the {season} season.", hammer: "{name} has the hammer.", ends: "By end, {name}: {list}.", total: "Total", wonPens: "{name} won {a}-{b} on penalties.", pens: "{a}-{b} pens", on: "Drawn as", sep: ", ", stop: ".", colon: ": ", gap: " " },
|
|
203
203
|
budo: {
|
|
204
204
|
sides: { north: "north", south: "south", east: "east", west: "west" },
|
|
205
205
|
judo: "a judo contest area of {contest} by {contest} metres in a safety area {safety} metres wide, laid with tatami of {tatami}",
|
|
@@ -758,7 +758,7 @@ export const KYOGIJO_STRINGS = {
|
|
|
758
758
|
},
|
|
759
759
|
},
|
|
760
760
|
scoreTitle: "{away} {awayScore}、{home} {homeScore}",
|
|
761
|
-
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
761
|
+
said: { home: "ホーム", away: "アウェイ", won: "{name}の勝ちです。", tied: "同点です。", park: "外野フェンスは、{park}の公表されている距離をもとに描かれています。", officially: "正式名称は{name}です。", ground: "ピッチは、{ground}の公表されている広さをもとに描かれています。", groundDefault: "{ground}のピッチの広さは公表されていないため、国際標準の105×68mで描かれています。", arenaRink: "{arena}のアイスホッケーリンク", arenaCourt: "{arena}のバスケットボールコート", arenaRinkAbout: "アイスホッケーのリンク。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で角が丸く、レッドライン、ブルーライン、フェイスオフサークル、クリース、ゴールがあります", arenaCourtAbout: "バスケットボールのコート。{length}フィート(約{lengthM}m)×{width}フィート(約{widthM}m)で、ペイントエリア、スリーポイントライン、フリースローサークル、センターサークルがあります", arenaSize: "広さは、{arena}の公表されている数値をもとに描かれています。", arenaUnknown: "{arena}のリンクはNHLの規格より短かったとされていますが、広さが示されていないため、NHLの規格で描かれています。", real: "スタンドは、OpenStreetMapの建物の輪郭((c) OpenStreetMap contributors)の内側に収まるように、{arena}の{seats}席・{tiers}層を推定して描いたもので、アリーナの設計図ではありません。", realBuilding: "リンクは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", realMapped: "リンクの向きはOpenStreetMapに記録された氷面のとおりで、矢印は北を指します。", realNone: "リンクの向きは分かっていないため、東西に置き、北を上にしています。", stadium: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層のスタンドを推定して描いています。スタジアムの設計図ではありません。", stadiumOpening: "フィールドは屋根の開口部(フィールドと同じ大きさ)に沿って置いており、矢印は北を指します。", stadiumBuilding: "フィールドは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumNone: "フィールドの向きは分かっていないため、東西に置き、北を上にしています。", stadiumHall: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席・{tiers}層の客席を推定して描いています。ホールの設計図ではありません。", stadiumRingBuilding: "リングは建物の長い軸に沿って置いており(推定)、矢印は北を指します。", stadiumPitchPark: "ホームプレートの向きは、OpenStreetMapに記録されたグラウンドの芝生を、公表されているフェンスの形に合わせて求めており、矢印は北を指します。", stadiumNonePark: "ホームプレートの向きは分かっていないため、建物に収まる向きでグラウンドを描いており、北を示す矢印はありません。", stadiumPark: "{stadium}の建物の輪郭(OpenStreetMap、(c) OpenStreetMap contributors)の内側に、{seats}席のスタンドを推定して描いています。球場の設計図ではありません。", stadiumPitch: "フィールドの向きはOpenStreetMapに記録されたピッチのとおりで、矢印は北を指します。", stadiumInfield: "ホームプレートの向きは、OpenStreetMapに記録された内野と建物の輪郭から推定しており、矢印は北を指します。", district: "OpenStreetMapの建物の輪郭をもとに、{count}か所の実在の会場を実際の位置と向きのとおりに並べた配置図です。{mode}。", districtNeighbours: "建物のすぐ近くまで寄せて描いています", districtCity: "実際の距離を保ち、1km方眼と縮尺を添えています", districtCredit: "出典:", era: "ラインやマークは{season}シーズンのものです。", hammer: "{name}がハンマー(最後の1投)を持っています。", ends: "{name}のエンドごとの得点:{list}。", total: "合計", wonPens: "{name}がPK戦を{a}-{b}で制しました。", pens: "PK戦 {a}-{b}", on: "図の内容", sep: "、", stop: "。", colon: ":", gap: "" },
|
|
762
762
|
budo: {
|
|
763
763
|
sides: { north: "北", south: "南", east: "東", west: "西" },
|
|
764
764
|
judo: "柔道の試合場。試合場内は{contest}m四方、幅{safety}mの安全地帯があり、{tatami}の畳を敷いています",
|
|
@@ -107,7 +107,8 @@ export function baseball(points, o, era, diamond = MLB_DIAMOND) {
|
|
|
107
107
|
base += el("path", { class: ft(o, "infield-dirt", "ky-dirt"), d: `M0 0L${num(ua)} ${num(-ua)}A${num(skin)} ${num(skin)} 0 0 0 ${num(-ua)} ${num(-ua)}Z` });
|
|
108
108
|
const centreV = d.base * SQ;
|
|
109
109
|
const half = centreV - 4.5 * Math.SQRT2;
|
|
110
|
-
|
|
110
|
+
if (o.dirtInfield !== true)
|
|
111
|
+
base += el("path", { class: ft(o, "infield-grass", "ky-infield"), d: `M0 ${num(-(centreV + half))}L${num(half)} ${num(-centreV)}L0 ${num(-(centreV - half))}L${num(-half)} ${num(-centreV)}Z` });
|
|
111
112
|
const cuts = [
|
|
112
113
|
[0, 0, MLB.homeCircleRadius],
|
|
113
114
|
[centreV, centreV, MLB.baseCircleRadius],
|
|
@@ -203,6 +203,8 @@ export interface BuildOptions {
|
|
|
203
203
|
landmarks?: boolean;
|
|
204
204
|
/** For baseball: what the park's article adds to the plain field (the warning track's width, the walls, the quirks). */
|
|
205
205
|
ballpark?: Ballpark;
|
|
206
|
+
/** For baseball: the infield is all dirt, with no grass inside the bases (a real ballpark's, where its sources say so: Koshien Stadium's). */
|
|
207
|
+
dirtInfield?: boolean;
|
|
206
208
|
}
|
|
207
209
|
/**
|
|
208
210
|
* The class of an element that is a named feature of the surface: `cls` and, for each feature name that has a colour of its own, `ky-f-<name>`.
|
package/dist/turn.style.js
CHANGED
|
@@ -5,9 +5,9 @@ import { KYOGIJO_TURN_BOARD_STYLE } from "./overlay.js";
|
|
|
5
5
|
* defaults are in `:where()`, which has no weight, so a page's own rule always wins; the colours are custom properties (`--ky-up-*`) a page can set.
|
|
6
6
|
*/
|
|
7
7
|
export const KYOGIJO_TURN_STYLE = `
|
|
8
|
-
:where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT}}
|
|
9
|
-
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}}
|
|
10
|
-
:where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}
|
|
8
|
+
:where(.ky-turn){--ky-left:#2f6db0;--ky-right:#b5452c;--ky-cr-home:#b5452c;--ky-cr-away:#2f6db0;--ky-cr-bed:#4a525d;--ky-cr-empty:#7d8794;--ky-cr-gap:#14181d;--ky-cr-conc:#8c929b;--ky-cr-dim:1;--ky-up-roof:rgba(214,218,212,.6);--ky-up-roofline:rgba(40,46,42,.55);--ky-up-fascia:#9aa09a;--ky-up-ivy:#5b8a45;--ky-up-door:#8a6a46;--ky-up-window:rgba(150,200,240,.55);--ky-up-sash:#f2f0e6;--ky-up-banner:#26211d;--ky-up-bannerline:#c8b88a;--ky-up-room:#efe8d6;--ky-up-roomback:#d6cdb9;--ky-up-ceiling:#dedcd5;--ky-up-railw:#fbfaf6;--ky-up-wainline:#6b4a2a;--ky-up-riser:#3f342c;--ky-up-slab:#d8cfba;--ky-up-slabedge:#c2b69c;--ky-up-seatc:#46342a;--ky-up-seatb:#2d211b;--ky-up-dais:#a0683a;--ky-up-dais-side:#6a4326;--ky-up-shrine:#c9b48f;--ky-up-shrine-side:#8f7a55;--ky-up-gal:#4d4037;--ky-up-aisle:#dcc196;--ky-up-panel:rgba(95,58,22,.3);--ky-up-alcove:#b58a52;--ky-up-purple:#5b2a6e;--ky-up-clock:#ffffff;--ky-up-rail:#3a2c1f;--ky-up-matedge:#2a2a26;--ky-up-sbframe:#1a1f1c;--ky-up-sbedge:#5b645e;--ky-up-sbline:#59615b;--ky-up-pole:#e9e7de;--ky-up-white:#f6f4ec;--ky-up-yellow:#f2c230;--ky-up-red:#c8372d;--ky-up-orange:#f26a1b;--ky-up-wood:#e3d4b4;--ky-up-leg:#474e49;--ky-up-pad:#2f6db0;--ky-up-cagepad:#2a2d32;--ky-up-table:#18243f;--ky-up-foot:rgba(0,0,0,.25);--ky-up-ground:#d9d5c7;--ky-up-wall:#2f4a36;--ky-up-clay:#a8703f;--ky-up-cap:#1d2a22;--ky-up-board:#ece9df;--ky-up-glass:rgba(138,190,232,.26);--ky-up-backboard:rgba(255,255,255,.88);--ky-up-outline:#f2f0e6;--ky-up-rim:#e8591a;--ky-up-mesh:rgba(24,30,27,.7);--ky-up-netfill:rgba(255,255,255,.2);--ky-up-tape:#fbfaf4;--ky-turn-bg:transparent;--ky-turn-ink:#1f2320;--ky-turn-rule:rgba(31,35,32,.22);--ky-turn-chip:rgba(255,255,255,.86);--ky-turn-chip-on:#1f2320;--ky-turn-chip-on-ink:#fff;${JUDGING_LIGHT}}
|
|
9
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-turn:not([data-theme=light])){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}}
|
|
10
|
+
:where(html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){--ky-cr-bed:#242a32;--ky-cr-empty:#4a535f;--ky-cr-gap:#050709;--ky-cr-conc:#2a3038;--ky-cr-dim:.82;--ky-up-roof:rgba(60,66,62,.6);--ky-up-roofline:rgba(200,206,200,.5);--ky-up-fascia:#505650;--ky-up-ivy:#3d6633;--ky-up-door:#6a4f33;--ky-up-gal:#2d2621;--ky-up-aisle:#8f7752;--ky-up-alcove:#7d5c36;--ky-up-purple:#43204f;--ky-up-clock:#cfcbc0;--ky-up-room:#6d675a;--ky-up-roomback:#58534a;--ky-up-ceiling:#8c8a82;--ky-up-railw:#d9d6cc;--ky-up-riser:#251f1b;--ky-up-slab:#6b6455;--ky-up-slabedge:#585143;--ky-up-seatc:#2f231c;--ky-up-seatb:#1c1411;--ky-up-dais:#7d5230;--ky-up-dais-side:#52361f;--ky-up-sbframe:#0c0f0d;--ky-up-sbedge:#7a847d;--ky-up-sbline:#8a928c;--ky-up-pole:#d6d4ca;--ky-up-white:#e8e6dc;--ky-up-wood:#a89873;--ky-up-leg:#8a928c;--ky-up-ground:#242823;--ky-up-cagepad:#8f969f;--ky-up-wall:#4d6b55;--ky-up-clay:#7d5231;--ky-up-cap:#c9d2cc;--ky-up-board:#8d928b;--ky-up-glass:rgba(150,200,240,.2);--ky-up-backboard:rgba(255,255,255,.34);--ky-up-outline:#e7e4d8;--ky-up-mesh:rgba(236,236,226,.62);--ky-up-netfill:rgba(255,255,255,.08);--ky-up-tape:#efede2;--ky-turn-ink:#ece8dc;--ky-turn-rule:rgba(236,232,220,.28);--ky-turn-chip:rgba(24,28,25,.82);--ky-turn-chip-on:#ece8dc;--ky-turn-chip-on-ink:#1f2320;${JUDGING_DARK}}
|
|
11
11
|
.ky-turn{position:relative;display:block;width:100%;color:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);background:var(--ky-turn-bg)}
|
|
12
12
|
.ky-turn-view{position:relative;isolation:isolate;width:100%;aspect-ratio:var(--ky-turn-ratio,16/10);min-height:260px;overflow:hidden;touch-action:pan-y;cursor:grab;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:12px;outline-offset:2px}
|
|
13
13
|
.ky-turn[data-touch=turn] .ky-turn-view{touch-action:none}
|
|
@@ -67,6 +67,7 @@ export const KYOGIJO_TURN_STYLE = `
|
|
|
67
67
|
.ky-turn .u-conc{fill:var(--ky-cr-conc)}
|
|
68
68
|
.ky-turn .u-fascia{fill:var(--ky-up-fascia)}
|
|
69
69
|
.ky-turn .u-shell{fill:var(--ky-up-fascia);stroke:var(--ky-up-roofline);stroke-width:.6}
|
|
70
|
+
.ky-turn .u-ivy{fill:var(--ky-up-ivy)}
|
|
70
71
|
.ky-turn .u-roof{fill:var(--ky-up-roof);stroke:var(--ky-up-roofline);stroke-width:.8;stroke-linejoin:round}
|
|
71
72
|
.ky-turn .u-signplate{fill:var(--ky-turn-bg);fill-opacity:.9;stroke:var(--ky-turn-rule);stroke-width:.5}
|
|
72
73
|
.ky-turn .u-sign{fill:var(--ky-turn-ink);font-family:var(--ky-font,system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif);font-weight:600}
|
package/dist/types.d.ts
CHANGED
|
@@ -685,7 +685,7 @@ export interface SurfaceOptions {
|
|
|
685
685
|
pitch?: SoccerPitchId | SoccerSize;
|
|
686
686
|
/** The soccer ground to draw: a key of `GROUNDS`. The pitch has the size its article publishes (the international 105 by 68 m where none is), and the drawing carries the ground's name for `year`. A `pitch` of your own wins over the ground's size. */
|
|
687
687
|
ground?: string;
|
|
688
|
-
/** The stadium to draw a football field (`sport: "cfl"`)
|
|
688
|
+
/** The stadium to draw a football field (`sport: "cfl"`), a soccer pitch or a boxing ring (`sport: "boxing"`, in a hall) in, for `crowd: { real: true }`: a key of `STADIUMS` (`bc-place`, `korakuen-hall`). A soccer drawing with a `ground` that is a stadium of `STADIUMS` needs no other. */
|
|
689
689
|
stadium?: string;
|
|
690
690
|
/** The arena to draw an NHL rink or an NBA court of: a key of `ARENAS`. The rink or the court has the size its sources publish where that differs from the league's (the league's where none does), the markings are those of `year`, and the drawing carries the arena's name for `year`. A `league` of your own wins over the arena's size. */
|
|
691
691
|
arena?: string;
|
package/dist/venue.real.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare function realBuilding(real: RealShell, top: StandRing | undefined
|
|
|
17
17
|
}): Items;
|
|
18
18
|
/** 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). */
|
|
19
19
|
export declare const CANOPY_NOTE = "low over the field, high over the back of the stands";
|
|
20
|
+
/** How many rings of pieces an air-supported roof is drawn in, from the rim in to the crown (ours: the roof is smooth, the drawing is faceted). */
|
|
21
|
+
export declare const DOME_RINGS = 4;
|
|
20
22
|
/** 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). */
|
|
21
23
|
export declare const POD: {
|
|
22
24
|
readonly share: 0.24;
|
package/dist/venue.real.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
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),
|
|
4
|
-
// (`venue: { roof: true }`) and give way to the eye as the other walls do.
|
|
3
|
+
// stands that leave the field open (Lumen Field's), retractable panels high over a ballpark (T-Mobile Park's, resting over its east side when `roofOpen` is asked), an air-supported dome (Tokyo Dome's: a smooth lens of
|
|
4
|
+
// membrane from the rim of the walls to a crown, which never opens), or no roof at all (Koshien Stadium's). All are asked for with the roof (`venue: { roof: true }`) and give way to the eye as the other walls do.
|
|
5
|
+
// Pure: data in, planes out.
|
|
5
6
|
import { rayAt } from "./crowd.js";
|
|
6
7
|
import { n, shy, wall } from "./turn.kit.js";
|
|
7
8
|
import { pointList, raked } from "./venue.geom.js";
|
|
@@ -72,7 +73,7 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
72
73
|
if (long < 1)
|
|
73
74
|
return;
|
|
74
75
|
const high = lift + (real.wall ?? roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
|
|
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 });
|
|
76
|
+
planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : "u-shell"}" width="${n(long)}" height="${n(high)}"/>`), centre), fadeTo: 0.02 });
|
|
76
77
|
});
|
|
77
78
|
if (parts.walls && planes.length > 0)
|
|
78
79
|
items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
|
|
@@ -82,6 +83,15 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
|
|
|
82
83
|
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
83
84
|
return items;
|
|
84
85
|
}
|
|
86
|
+
if (parts.roof && real.roof === "dome" && real.dome !== undefined) {
|
|
87
|
+
const planes = domeRoof(real.dome, lift, steps);
|
|
88
|
+
if (planes.length > 0)
|
|
89
|
+
items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
|
|
90
|
+
return items;
|
|
91
|
+
}
|
|
92
|
+
// An open-air ballpark has walls and no roof.
|
|
93
|
+
if (real.roof === "open")
|
|
94
|
+
return items;
|
|
85
95
|
if (parts.roof && real.roof === "panels" && real.panels !== undefined) {
|
|
86
96
|
const planes = panelsRoof(real.panels, c, lift, steps, open);
|
|
87
97
|
if (planes.length > 0)
|
|
@@ -187,6 +197,46 @@ function canopyRoof(canopies, c, lift, steps) {
|
|
|
187
197
|
});
|
|
188
198
|
return planes;
|
|
189
199
|
}
|
|
200
|
+
/** How many rings of pieces an air-supported roof is drawn in, from the rim in to the crown (ours: the roof is smooth, the drawing is faceted). */
|
|
201
|
+
export const DOME_RINGS = 4;
|
|
202
|
+
/**
|
|
203
|
+
* An air-supported roof: the membrane over the rim of the walls, a lens that stands `low` over the surface at the rim and rises to the `crown` over the middle (a shallow cap: the height falls with the square of the way
|
|
204
|
+
* out to the rim), in rings of pieces between rays from the middle of the rim. Its pieces are drawn as the roof is (a pale skin with a thin line), so that the net of wires it is made on shows.
|
|
205
|
+
*/
|
|
206
|
+
function domeRoof(dome, lift, steps) {
|
|
207
|
+
const planes = [];
|
|
208
|
+
const mid = [dome.rim.reduce((s, p) => s + p[0], 0) / dome.rim.length, dome.rim.reduce((s, p) => s + p[1], 0) / dome.rim.length];
|
|
209
|
+
const rim = Array.from({ length: steps }, (_, k) => firstHit(dome.rim, mid, (2 * Math.PI * k) / steps) ?? mid);
|
|
210
|
+
// The rings' share of the way out (1 is the rim), the outermost first, and the height there.
|
|
211
|
+
const shares = Array.from({ length: DOME_RINGS + 1 }, (_, j) => 1 - (j * 0.85) / DOME_RINGS);
|
|
212
|
+
// (The innermost ring is the crown itself, the flat cap's height, so that the roof reaches the height OpenStreetMap maps.)
|
|
213
|
+
const at = (share) => lift + (share === shares[DOME_RINGS] ? dome.crown : dome.low + (dome.crown - dome.low) * (1 - share * share));
|
|
214
|
+
const ring = (share) => rim.map((p) => [mid[0] + (p[0] - mid[0]) * share, mid[1] + (p[1] - mid[1]) * share]);
|
|
215
|
+
for (let j = 0; j < DOME_RINGS; j += 1) {
|
|
216
|
+
const [outer, inner] = [ring(shares[j]), ring(shares[j + 1])];
|
|
217
|
+
const [zo, zi] = [at(shares[j]), at(shares[j + 1])];
|
|
218
|
+
for (let k = 0; k < steps; k += 1) {
|
|
219
|
+
const [a, b] = [outer[k], outer[(k + 1) % steps]];
|
|
220
|
+
const [ai, bi] = [inner[k], inner[(k + 1) % steps]];
|
|
221
|
+
const [ex, ey] = [b[0] - a[0], b[1] - a[1]];
|
|
222
|
+
const el = Math.hypot(ex, ey);
|
|
223
|
+
if (el < 1e-6)
|
|
224
|
+
continue;
|
|
225
|
+
const slope = slopeThrough([[ai[0], ai[1], zi], [bi[0], bi[1], zi], [b[0], b[1], zo], [a[0], a[1], zo]]);
|
|
226
|
+
const centre = [(a[0] + b[0] + ai[0] + bi[0]) / 4, (a[1] + b[1] + ai[1] + bi[1]) / 4];
|
|
227
|
+
const away = Math.hypot(centre[0] - mid[0], centre[1] - mid[1]) || 1;
|
|
228
|
+
const plane = raked(`roof-${j}-${k}`, "roof", [ai, bi, b, a], slope, [ex / el, ey / el], (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>`);
|
|
229
|
+
if (plane !== undefined)
|
|
230
|
+
planes.push({ ...plane, shy: [(centre[0] - mid[0]) / away, (centre[1] - mid[1]) / away], fadeTo: 0.2 });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// The crown: a flat cap over the middle.
|
|
234
|
+
const top = ring(shares[DOME_RINGS]);
|
|
235
|
+
const cap = raked("roof-crown", "roof", top, { x0: mid[0], y0: mid[1], z0: at(shares[DOME_RINGS]), gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>`);
|
|
236
|
+
if (cap !== undefined)
|
|
237
|
+
planes.push({ ...cap, fadeTo: 0.2 });
|
|
238
|
+
return planes;
|
|
239
|
+
}
|
|
190
240
|
/** 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
241
|
const PANEL_CROWN = 0.3;
|
|
192
242
|
/** How thick a parked section is, in feet (ours). */
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.33.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The package's version: the same as package.json's, and a test holds them to each other. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.33.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kyogijo-stadiums",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Kyogijo Stadiums 競技場, 40 sports' surfaces as SVG, drawn to scale from the rulebooks: NFL, CFL and IFAF football fields, an NHL rink, an NBA court, MLB ballparks, soccer pitches, curling sheets, athletics stadiums, speed skating, short track and figure skating rinks, swimming pools, water polo fields, diving wells, artistic swimming areas, and the courts and fields of tennis, volleyball (indoor, beach and sitting), badminton, pickleball, table tennis, padel, squash, racquetball, sepak takraw, teqball, roundnet, netball, lacrosse, rugby, Australian football, Gaelic games, cricket, field hockey and handball, and the budo of judo, karate, kendo, sumo and kyudo with a dojo of tatami laid as real units, the MMA cage, with each team's name and logo on its side and the score in the middle, stands and a crowd, a pageant of flags, and a 3D view. Light and dark, accessible, a compact badge for lists, a helper for React and a custom element. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sports",
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
"devDependencies": {
|
|
318
318
|
"@eslint/js": "^9.0.0",
|
|
319
319
|
"@johnmorrisdotca/chizu": "1.4.0",
|
|
320
|
-
"@johnmorrisdotca/dougu-sports": "0.
|
|
320
|
+
"@johnmorrisdotca/dougu-sports": "0.13.0",
|
|
321
321
|
"@johnmorrisdotca/hata": "1.5.0",
|
|
322
322
|
"@johnmorrisdotca/taisen-brackets": "0.17.1",
|
|
323
323
|
"@playwright/test": "^1.63.0",
|