@johnmorrisdotca/kyogijo-stadiums 0.40.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/NOTICE.md +17 -0
  3. package/README.md +1 -3
  4. package/dist/context.d.ts +141 -0
  5. package/dist/context.js +290 -0
  6. package/dist/crowd.d.ts +33 -1
  7. package/dist/crowd.draw.d.ts +1 -1
  8. package/dist/crowd.draw.js +16 -2
  9. package/dist/crowd.js +9 -1
  10. package/dist/district-entry.d.ts +14 -0
  11. package/dist/district-entry.js +9 -0
  12. package/dist/district.d.ts +54 -1
  13. package/dist/district.dom.d.ts +31 -0
  14. package/dist/district.dom.js +52 -0
  15. package/dist/district.js +71 -11
  16. package/dist/district.turn.js +30 -6
  17. package/dist/draw.js +37 -13
  18. package/dist/gates.d.ts +56 -0
  19. package/dist/gates.js +95 -0
  20. package/dist/halls.d.ts +2 -0
  21. package/dist/halls.js +1 -1
  22. package/dist/kodokan.d.ts +28 -0
  23. package/dist/kodokan.data.d.ts +18 -0
  24. package/dist/kodokan.data.js +18 -0
  25. package/dist/kodokan.js +34 -0
  26. package/dist/osm-entry.d.ts +2 -0
  27. package/dist/osm-entry.js +2 -0
  28. package/dist/osm.context.data.d.ts +10 -0
  29. package/dist/osm.context.data.js +1107 -0
  30. package/dist/outside.d.ts +43 -0
  31. package/dist/outside.dom.d.ts +44 -0
  32. package/dist/outside.dom.js +203 -0
  33. package/dist/outside.js +227 -0
  34. package/dist/outside.paint.d.ts +57 -0
  35. package/dist/outside.paint.js +88 -0
  36. package/dist/plateau-entry.d.ts +3 -0
  37. package/dist/plateau-entry.js +3 -0
  38. package/dist/plateau.credit.d.ts +5 -0
  39. package/dist/plateau.credit.js +7 -0
  40. package/dist/plateau.data.d.ts +7 -0
  41. package/dist/plateau.data.js +757 -0
  42. package/dist/stadiums.d.ts +15 -1
  43. package/dist/stadiums.data.js +2 -1
  44. package/dist/stadiums.js +48 -12
  45. package/dist/strings.d.ts +3 -1
  46. package/dist/strings.js +4 -4
  47. package/dist/surfaces/surface.d.ts +5 -0
  48. package/dist/turn.dom.d.ts +10 -0
  49. package/dist/turn.dom.js +21 -0
  50. package/dist/turn.kit.d.ts +10 -3
  51. package/dist/turn.kit.js +7 -0
  52. package/dist/turn.markup.js +7 -2
  53. package/dist/turn.model.d.ts +3 -0
  54. package/dist/turn.style.js +15 -3
  55. package/dist/uprights.js +9 -1
  56. package/dist/venue.js +9 -3
  57. package/dist/venue.real.d.ts +22 -1
  58. package/dist/venue.real.js +76 -7
  59. package/dist/venues.d.ts +9 -1
  60. package/dist/venues.data.js +2 -1
  61. package/dist/venues.js +1 -1
  62. package/dist/version.d.ts +1 -1
  63. package/dist/version.js +1 -1
  64. package/dist/walk.d.ts +56 -0
  65. package/dist/walk.data.d.ts +4 -0
  66. package/dist/walk.data.js +1295 -0
  67. package/dist/walk.js +120 -0
  68. package/package.json +17 -1
@@ -2,8 +2,9 @@
2
2
  // (`buildTurn`: the stands, the walls and the roof, the goals and posts of its field), its planes are turned about the middle of its surface and moved to where the plan puts it, and the floor is the district's flat
3
3
  // plan drawn with each venue's building only. The model it makes is a `TurnModel`, so the camera, the controls, the pointer, the auto-turn and the steady fit of every other 3D view are this one's; its
4
4
  // buildings are stacked building by building (`Plane.group`), the far one first, so that a roof never shows through a nearer building.
5
- import { describeDistrict, districtId, drawDistrict, drawMember, planDistrict, surfaceCentre } from "./district.js";
6
- import { n, plane } from "./turn.kit.js";
5
+ import { describeDistrict, districtId, drawDistrict, drawMember, nestedDrawing, planDistrict, surfaceCentre } from "./district.js";
6
+ import { blockPlanes, contextUprights, shellBlock } from "./context.js";
7
+ import { APART, n, plane } from "./turn.kit.js";
7
8
  import { TURN_PRESETS, wrap } from "./turn.camera.js";
8
9
  import { mountTurn } from "./turn.dom.js";
9
10
  import { turnMarkupOf } from "./turn.markup.js";
@@ -11,6 +12,7 @@ import { buildTurn, lightingOf, openingView, sceneBox, WORLD_PX } from "./turn.m
11
12
  import { planesOf } from "./uprights.js";
12
13
  import { FEET_PER_METRE } from "./geometry.js";
13
14
  import { hullOf } from "./venues.geo.js";
15
+ import { hasOutside, outsideCrowd, outsideLevelOf } from "./outside.js";
14
16
  /** The four viewpoints of a district, turned with the map: the main camera looks from the south over the district (north at the top of the page), the end-on view from the west and the low corner from the south-west. */
15
17
  export function districtPresets(plan) {
16
18
  const turn = plan.turn;
@@ -27,7 +29,7 @@ const rot = ([x, y], degrees) => {
27
29
  return [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
28
30
  };
29
31
  /** A plane of a venue's own frame (its surface's middle at `centre`) put in the district's: turned by the venue's angle about that middle and moved to where the plan puts it. */
30
- function placeInDistrict(p, placed, centre, prefix, group) {
32
+ function placeInDistrict(p, placed, centre, prefix, group, lift = 0) {
31
33
  const at = (x, y) => {
32
34
  const [rx, ry] = rot([x - centre[0], y - centre[1]], placed.angle);
33
35
  return [rx + placed.at[0], ry + placed.at[1]];
@@ -36,7 +38,7 @@ function placeInDistrict(p, placed, centre, prefix, group) {
36
38
  const [ox, oy] = at(p.o[0], p.o[1]);
37
39
  const [ux, uy] = dir(p.u[0], p.u[1]);
38
40
  const [vx, vy] = dir(p.v[0], p.v[1]);
39
- const moved = { ...p, id: `${prefix}${p.id}`, o: [ox, oy, p.o[2]], u: [ux, uy, p.u[2]], v: [vx, vy, p.v[2]], group };
41
+ const moved = { ...p, id: `${prefix}${p.id}`, o: [ox, oy, p.o[2] + lift], u: [ux, uy, p.u[2]], v: [vx, vy, p.v[2]], group };
40
42
  if (p.shy !== undefined)
41
43
  moved.shy = dir(p.shy[0], p.shy[1]);
42
44
  if (p.crop === true) {
@@ -77,19 +79,39 @@ export function buildDistrictTurn(options, settings = {}) {
77
79
  const model = buildTurn({ ...member.options, language, ...(options.theme === undefined ? {} : { theme: options.theme }), id: `${base}-m${i}` }, { ...settings, id: memberId, venue: { roof: true, ...(typeof settings.venue === "object" ? settings.venue : {}) }, preset: undefined, view: undefined });
78
80
  const centre = surfaceCentre(member.options);
79
81
  const prefix = `m${i}-`;
82
+ // A venue on an upper floor (the Kodokan's main dojo) stands that high over the ground, on a floor drawn in the air; the ground under it is left bare.
83
+ const lift = placed.member.elevation ?? 0;
80
84
  for (const item of model.uprights.items) {
81
- const planes = item.planes.filter((p) => p.kind !== "ground").map((p) => placeInDistrict(p, placed, centre, prefix, i));
85
+ const planes = item.planes.filter((p) => p.kind !== "ground").map((p) => placeInDistrict(p, placed, centre, prefix, i, lift));
82
86
  if (planes.length > 0) {
83
87
  const [ax, ay] = item.at;
84
88
  const [rx, ry] = rot([ax - centre[0], ay - centre[1]], placed.angle);
85
- items.push({ ...item, id: `${prefix}${item.id}`, at: [rx + placed.at[0], ry + placed.at[1]], planes });
89
+ items.push({ ...item, id: `${prefix}${item.id}`, at: [rx + placed.at[0], ry + placed.at[1]], height: item.height + lift, planes });
86
90
  }
87
91
  }
92
+ if (lift > 0) {
93
+ const { markup, box, styles } = nestedDrawing(drawMember(member, { ...options, id: base }, i, base, { floor: true }));
94
+ for (const block of styles)
95
+ if (!defs.includes(block))
96
+ defs.push(block);
97
+ const floor = plane("floor", "floor", [box.x, box.y, 0], [box.w, 0, 0], [0, box.h, 0], `<g transform="translate(${n(-box.x)} ${n(-box.y)})">${markup}</g>`);
98
+ items.push({ id: `${prefix}floor`, kind: "deck", at: placed.at, height: lift, planes: [placeInDistrict(floor, placed, centre, prefix, i, lift)] });
99
+ }
100
+ // The building it stands in: see-through (its walls give way to the eye) or solid, in the venue's group so that it is stacked with it.
101
+ if (member.shell !== undefined && options.building !== "none") {
102
+ const block = shellBlock(member.key, placed.footprint, member.shell.height, options.building === "solid" ? undefined : placed.at);
103
+ const planes = blockPlanes(block, `${prefix}shell-`).map((p) => ({ ...p, group: i }));
104
+ items.push({ id: `${prefix}shell`, kind: "blocks", at: placed.at, height: member.shell.height, planes });
105
+ }
88
106
  if (model.defs !== "")
89
107
  defs.push(model.defs);
90
108
  if (crowdStyle === "" && model.crowdStyle !== "")
91
109
  crowdStyle = model.crowdStyle;
92
110
  });
111
+ // The buildings round the venues stand apart from them: each block's planes are of no venue's group.
112
+ if (plan.context !== undefined)
113
+ for (const item of contextUprights(plan.context))
114
+ items.push({ ...item, planes: item.planes.map((p) => ({ ...p, group: APART })) });
93
115
  if (labels) {
94
116
  const size = Math.max(plan.view.w, plan.view.h) * (plan.mode === "city" ? 0.016 : 0.022);
95
117
  plan.members.forEach((placed, i) => {
@@ -136,6 +158,8 @@ export function buildDistrictTurn(options, settings = {}) {
136
158
  lens: 12,
137
159
  flat: drawDistrict(options, plan),
138
160
  groups: plan.members.map((p) => ({ hull: hullOf(p.footprint) })),
161
+ // The crowd outside, when the district asks for one and has a mask to put it on: the mount paints it on a canvas on the floor.
162
+ ...(options.outside !== undefined && hasOutside(plan) ? { outside: { level: outsideLevelOf(options), crowd: () => outsideCrowd(plan), viewBox: { x: view.x, y: view.y, w: view.w, h: view.h } } } : {}),
139
163
  };
140
164
  }
141
165
  /**
package/dist/draw.js CHANGED
@@ -457,8 +457,13 @@ function standsFor(o, surface, crowd) {
457
457
  return crowdLayout({ sport, box, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: venueBowl(venue, box) });
458
458
  // A football field or a soccer pitch in a real stadium: its building and a bowl of the configuration's seats.
459
459
  const real = realStadiumOf(o, sport, crowd.real);
460
- if (real !== undefined)
461
- return crowdLayout({ sport, box, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: stadiumBowl(real.stadium, real.config, box) });
460
+ if (real !== undefined) {
461
+ const stands = (at) => crowdLayout({ sport, box: at, crowd: crowd.awayEnd === "auto" ? { ...crowd, awayEnd: "none" } : crowd, awaySide, real: stadiumBowl(real.stadium, real.config, at) });
462
+ if (!venueFrame(o, crowd, surface))
463
+ return stands(box);
464
+ // In the venue's frame the middle of the field is the origin, so the same building, bowl and crowd are laid out whichever field is in it.
465
+ return { ...stands({ x: -box.w / 2, y: -box.h / 2, w: box.w, h: box.h }), shift: [box.x + box.w / 2, box.y + box.h / 2] };
466
+ }
462
467
  // A dojo's crowd sits in its gallery, from the room's balcony data (in feet here); the arenas (the Budokan, the Kokugikan) keep their bowls.
463
468
  const hall = sport === "dojo" ? dojoGallery(o) : undefined;
464
469
  const gallery = hall === undefined ? undefined : { offset: hall.offset / 0.3048, depth: hall.depth / 0.3048, height: [hall.height[0] / 0.3048, hall.height[1] / 0.3048], rows: hall.rows, aisles: hall.aisles };
@@ -476,6 +481,13 @@ function stadiumHasConfigs(o, crowd) {
476
481
  const real = realStadiumOf(o, chooseLeague(o).sport, crowd.real);
477
482
  return real !== undefined && stadiumKind(real.stadium) === "stadium" && stadiumConfigs(real.stadium).length > 1;
478
483
  }
484
+ /**
485
+ * Whether a stadium of more than one configuration is laid out in the venue's own frame: its field is placed in it, and the building, the bowl, the crowd and the view are the same numbers whichever field it is. Not for a portrait drawing,
486
+ * a cropped one, or one that asks for annotations, a situation, a pageant or the pointer, which are worked out in the surface's own frame.
487
+ */
488
+ function venueFrame(o, crowd, surface) {
489
+ return surface.turn === undefined && o.range === undefined && o.situation === undefined && o.pageant === undefined && o.pointer !== true && !annotated(o) && stadiumHasConfigs(o, crowd);
490
+ }
479
491
  /** A surface with the stands and the crowd round it: the ground is made as wide as the stands need, then the stands are laid on it under the surface. */
480
492
  function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
481
493
  let margin = extra.margin;
@@ -490,13 +502,6 @@ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
490
502
  }
491
503
  const surface = buildWhole(o, portrait, id, score, scored, { ...extra, margin });
492
504
  const stands = standsFor(o, surface, crowd);
493
- // A stadium that stages more than one field is framed by its building, once, whichever field is drawn: the view is the building's with a small margin, about the middle of the field.
494
- const field = stands.box;
495
- if (surface.turn === undefined && o.range === undefined && stadiumHasConfigs(o, crowd)) {
496
- const [cx, cy] = [field.x + field.w / 2, field.y + field.h / 2];
497
- const [left, right, top, bottom] = [field.w / 2 + stands.reach.left, field.w / 2 + stands.reach.right, field.h / 2 + stands.reach.top, field.h / 2 + stands.reach.bottom].map((d) => d + VENUE_PAD);
498
- surface.view = { x: cx - left, y: cy - top, w: left + right, h: top + bottom };
499
- }
500
505
  const seats = stands.seats;
501
506
  const ground = groundById(o.ground)?.capacity;
502
507
  const fine = crowd.detail === "fine" || (crowd.detail === "auto" && !smallDrawing(o));
@@ -505,6 +510,22 @@ function buildCrowded(o, portrait, id, score, scored, extra, crowd) {
505
510
  // A gallery is empty unless the options say how full: its seats are the detail.
506
511
  const taken = stands.gallery === true && crowd.density === undefined && crowd.attendance === undefined ? 0 : densityOf(crowd, seats, ground);
507
512
  const drawn = drawCrowd(stands, { id, crowd, density: taken, fine, ground: bg, team: teamColors(o) });
513
+ if (stands.shift !== undefined) {
514
+ // A stadium of more than one configuration: the ground, the building, the stands and the crowd are laid in the venue's own frame (the middle of the field at the origin), under a surface that is shifted to lie in it, and the view is the
515
+ // building's with a little ground round it: the same frame, the same numbers, the same crowd whichever field is drawn. What stands over the field comes back to the surface's frame.
516
+ const [sx, sy] = stands.shift;
517
+ const { reach, box } = stands;
518
+ const [left, right, top, bottom] = [box.w / 2 + reach.left, box.w / 2 + reach.right, box.h / 2 + reach.top, box.h / 2 + reach.bottom].map((d) => Math.round((d + VENUE_PAD) * 100) / 100);
519
+ const view = { x: -left, y: -top, w: left + right, h: top + bottom };
520
+ const apron = first.replace(/\sx="[^"]*"/, ` x="${view.x - 1}"`).replace(/\sy="[^"]*"/, ` y="${view.y - 1}"`).replace(/\swidth="[^"]*"/, ` width="${view.w + 2}"`).replace(/\sheight="[^"]*"/, ` height="${view.h + 2}"`);
521
+ surface.venue = { shift: [sx, sy], under: apron + drawn.markup };
522
+ surface.base = surface.base.slice(first.length);
523
+ surface.view = view;
524
+ surface.defs += drawn.defs;
525
+ surface.lines += drawn.over === "" ? "" : el("g", { transform: `translate(${sx.toFixed(4)} ${sy.toFixed(4)})` }, drawn.over);
526
+ surface.crowdMargin = margin;
527
+ return surface;
528
+ }
508
529
  surface.base = first + drawn.markup + surface.base.slice(first.length);
509
530
  surface.defs += drawn.defs;
510
531
  surface.lines += drawn.over;
@@ -1011,8 +1032,10 @@ function drawnKyogijo(o) {
1011
1032
  if (lines !== undefined)
1012
1033
  surface.lines = restyleLines(surface.lines, lines);
1013
1034
  // A ground (a pattern, a colour or a photograph) is laid on the apron, under the surface.
1014
- const ground = laidOn(surface.base, o.background, id);
1015
- grounded = ground.base !== surface.base;
1035
+ // A stadium laid out in the venue's frame has its ground, building and stands under the surface, which is shifted to lie in it.
1036
+ const venue = surface.venue;
1037
+ const ground = laidOn(venue === undefined ? surface.base : venue.under, o.background, id);
1038
+ grounded = ground.base !== (venue === undefined ? surface.base : venue.under);
1016
1039
  defs = surface.defs + ground.defs;
1017
1040
  // The situation: a tint under the lines, and the rest over them and the data (cut with the surface when it is cropped).
1018
1041
  const sit = situation === undefined
@@ -1026,7 +1049,7 @@ function drawnKyogijo(o) {
1026
1049
  strip: stripped && sized === "compact",
1027
1050
  });
1028
1051
  const cutSit = (markup) => (markup !== "" && surface.defs.includes(`id="${id}-rg"`) ? el("g", { "clip-path": `url(#${id}-rg)` }, markup) : markup);
1029
- let inner = ground.base + (sit === undefined ? "" : cutSit(sit.under));
1052
+ let inner = (venue === undefined ? ground.base : surface.base) + (sit === undefined ? "" : cutSit(sit.under));
1030
1053
  if (sit !== undefined)
1031
1054
  defs += sit.defs;
1032
1055
  let teams = "";
@@ -1110,7 +1133,8 @@ function drawnKyogijo(o) {
1110
1133
  else
1111
1134
  inner += plateMarkup(surface.plate, left, right, { competition: o.competition, foot: foot === "" ? undefined : foot }, { turned: surface.turned, parts, surfaceSport: sport });
1112
1135
  }
1113
- const group = el("g", { class: "ky-surface-group", transform: surface.turn, part: parts ? "surface" : undefined, ...(o.pointer === true ? fieldAttributes({ ...o, sport }) : {}) }, inner);
1136
+ const field = el("g", { class: "ky-surface-group", transform: venue === undefined ? surface.turn : `translate(${(-venue.shift[0]).toFixed(4)} ${(-venue.shift[1]).toFixed(4)})`, part: parts ? "surface" : undefined, ...(o.pointer === true ? fieldAttributes({ ...o, sport }) : {}) }, inner);
1137
+ const group = venue === undefined ? field : ground.base + field;
1114
1138
  const v = surface.view;
1115
1139
  if (isScore(o) && left !== undefined && right !== undefined && sized === "compact") {
1116
1140
  const badge = badgeMarkup(left, right, { sport, status, parts, id, situation: situation === undefined ? undefined : badgeSituation(situation, language) });
@@ -0,0 +1,56 @@
1
+ import { type LonLat } from "./venues.geo.ts";
2
+ /** How a gate is known: `documented` is what a source cited with it says; `plausible` is ours. */
3
+ export type GateBasis = "documented" | "plausible";
4
+ /** What is said of a venue's gate: the way it faces, and what is read from where. */
5
+ export interface GateFact {
6
+ /** A key that is the same on every run: `south`, `south-west-corner`. */
7
+ id: string;
8
+ /** The bearing of the gate's outward normal, degrees clockwise from north, 0 to under 360. */
9
+ bearing: number;
10
+ /** The name where a source gives one (`Gate G`); a plain description of where it is otherwise. */
11
+ name?: {
12
+ en: string;
13
+ ja: string;
14
+ };
15
+ basis: GateBasis;
16
+ read: string;
17
+ }
18
+ /** A gate, placed on a venue's outline. */
19
+ export interface Gate {
20
+ /** `bc-place:south`: the venue's id and the gate's. */
21
+ key: string;
22
+ venue: string;
23
+ id: string;
24
+ name: {
25
+ en: string;
26
+ ja: string;
27
+ };
28
+ /** Where it stands on the outline, `[longitude, latitude]`. */
29
+ at: LonLat;
30
+ /** The bearing of its outward normal, degrees clockwise from north: the way a queue at it runs. */
31
+ bearing: number;
32
+ basis: GateBasis;
33
+ read: string;
34
+ }
35
+ /**
36
+ * The facts of each venue's gates. BC Place: the English Wikipedia article (revision 1372065649) says a tear in the old roof was "close to Gate G on the south side where the roof meets the top of the concrete bowl", so there is a
37
+ * Gate G on the south side (documented); the other seven are ours, one to each side of the building, on the street sides the brief names (Pacific Boulevard to the south, Robson and Beatty Streets). Rogers Arena: the brief's corner
38
+ * entrance plazas on the Pat Quinn Way side, the south front and the two south corners, and one at the north-west, all ours.
39
+ */
40
+ export declare const GATE_FACTS: Readonly<Record<string, readonly GateFact[]>>;
41
+ /** The ids of the venues that have gates. */
42
+ export declare const GATE_VENUES: readonly string[];
43
+ /** The name of a gate in a language: the source's where there is one, else its side (`South-west gate`). */
44
+ export declare function gateName(fact: GateFact, language: "en" | "ja"): string;
45
+ /**
46
+ * The gates of a venue (`bc-place`, `rogers-arena`), each on the building's outline where a ray from the middle of the surface at its bearing meets it; empty for a venue with none. A gate whose ray misses the outline is left out
47
+ * (none does for the venues here: a test holds it).
48
+ * @example
49
+ * ```ts
50
+ * import { venueGates } from "@johnmorrisdotca/kyogijo-stadiums/district";
51
+ * const gates = venueGates("bc-place");
52
+ * console.log(gates.length, gates.filter((g) => g.basis === "documented").map((g) => g.name.en).join(", "));
53
+ * // 8 Gate G
54
+ * ```
55
+ */
56
+ export declare function venueGates(id: string): Gate[];
package/dist/gates.js ADDED
@@ -0,0 +1,95 @@
1
+ // The gates of a real venue: where the crowd goes in. A gate is a point on the building's outline (OpenStreetMap's, ODbL 1.0) with the way it faces (the bearing of its outward normal, degrees clockwise from north), a name in English
2
+ // and Japanese, and how it is known: `documented` where a source cited with it says it, `plausible` where it is ours (a gate on each street side, put where a ray from the middle of the surface at the bearing meets the outline).
3
+ // The data is the gates' list of facts (`GATE_FACTS`, with what each cites); the positions are worked out from the outline, so a venue whose outline changes keeps its gates on it. This is the data the crowd's timelapse will use
4
+ // for queues and entrances: where people stand in a line, which way the line runs (away from the gate along its bearing) and where the walkable ground starts. Pure: ids in, gates out.
5
+ import { stadiumById } from "./stadiums.js";
6
+ import { venueById } from "./venues.js";
7
+ import { lonLatFrom, metresFrom } from "./venues.geo.js";
8
+ const WIND = [
9
+ { en: "north", ja: "北" },
10
+ { en: "north-east", ja: "北東" },
11
+ { en: "east", ja: "東" },
12
+ { en: "south-east", ja: "南東" },
13
+ { en: "south", ja: "南" },
14
+ { en: "south-west", ja: "南西" },
15
+ { en: "west", ja: "西" },
16
+ { en: "north-west", ja: "北西" },
17
+ ];
18
+ const ours = (reason) => `ours: ${reason}`;
19
+ /** A gate on each of the eight sides of a building: ours, the sources say that a stadium has gates and not where each is. */
20
+ const SIDES = (venue) => WIND.map((_, i) => ({ id: WIND[i].en, bearing: i * 45, basis: "plausible", read: ours(`one gate on each of the eight sides of ${venue}'s building: the sources say that it has gates round the bowl and not where each is`) }));
21
+ /**
22
+ * The facts of each venue's gates. BC Place: the English Wikipedia article (revision 1372065649) says a tear in the old roof was "close to Gate G on the south side where the roof meets the top of the concrete bowl", so there is a
23
+ * Gate G on the south side (documented); the other seven are ours, one to each side of the building, on the street sides the brief names (Pacific Boulevard to the south, Robson and Beatty Streets). Rogers Arena: the brief's corner
24
+ * entrance plazas on the Pat Quinn Way side, the south front and the two south corners, and one at the north-west, all ours.
25
+ */
26
+ export const GATE_FACTS = Object.freeze({
27
+ "bc-place": SIDES("BC Place").map((g) => (g.id === "south" ? { ...g, name: { en: "Gate G", ja: "ゲートG" }, basis: "documented", read: "the English Wikipedia article on BC Place, revision 1372065649: a tear in the old roof 'close to Gate G on the south side'; where on the south side is ours" } : g)),
28
+ "rogers-arena": [
29
+ { id: "south", bearing: 180, basis: "plausible", read: ours("the main front of the building faces Pat Quinn Way to the south, as the brief says") },
30
+ { id: "south-west", bearing: 225, basis: "plausible", read: ours("a corner entrance plaza on the Pat Quinn Way side, as the brief says") },
31
+ { id: "south-east", bearing: 135, basis: "plausible", read: ours("a corner entrance plaza on the Pat Quinn Way side, as the brief says") },
32
+ { id: "north-west", bearing: 315, basis: "plausible", read: ours("a gate on the side of the Georgia Viaduct and the towers, one of those the brief asks for at the corners") },
33
+ ],
34
+ });
35
+ /** The ids of the venues that have gates. */
36
+ export const GATE_VENUES = Object.freeze(Object.keys(GATE_FACTS));
37
+ /** The point where a ray from the origin at a compass bearing first meets a ring (both in metres east and north of the origin), or `undefined`. */
38
+ function hit(ring, bearing) {
39
+ const a = (bearing * Math.PI) / 180;
40
+ const [dx, dy] = [Math.sin(a), Math.cos(a)];
41
+ let best;
42
+ ring.forEach(([ax, ay], i) => {
43
+ const [bx, by] = ring[(i + 1) % ring.length];
44
+ const [ex, ey] = [bx - ax, by - ay];
45
+ const den = dx * ey - dy * ex;
46
+ if (Math.abs(den) < 1e-12)
47
+ return;
48
+ const t = (ax * ey - ay * ex) / den;
49
+ const u = (ax * dy - ay * dx) / den;
50
+ if (t > 1e-6 && u >= 0 && u <= 1 && (best === undefined || t < best))
51
+ best = t;
52
+ });
53
+ return best === undefined ? undefined : [dx * best, dy * best];
54
+ }
55
+ /** The outline and the middle of a venue's surface that its gates are placed from, `[longitude, latitude]`: an arena's, or a stadium's. */
56
+ function outlineOf(id) {
57
+ const venue = venueById(id);
58
+ if (venue !== undefined)
59
+ return { ring: venue.osm.ring, centre: venue.osm.axis.centre };
60
+ const stadium = stadiumById(id);
61
+ return stadium === undefined ? undefined : { ring: stadium.osm.ring, centre: stadium.osm.axis.centre };
62
+ }
63
+ /** The name of a gate in a language: the source's where there is one, else its side (`South-west gate`). */
64
+ export function gateName(fact, language) {
65
+ if (fact.name !== undefined)
66
+ return fact.name[language];
67
+ const side = WIND.find((w) => w.en === fact.id.replace(/-corner$/, ""));
68
+ return language === "ja" ? `${side?.ja ?? fact.id}ゲート` : `${(side?.en ?? fact.id).replace(/^./, (c) => c.toUpperCase())} gate`;
69
+ }
70
+ /**
71
+ * The gates of a venue (`bc-place`, `rogers-arena`), each on the building's outline where a ray from the middle of the surface at its bearing meets it; empty for a venue with none. A gate whose ray misses the outline is left out
72
+ * (none does for the venues here: a test holds it).
73
+ * @example
74
+ * ```ts
75
+ * import { venueGates } from "@johnmorrisdotca/kyogijo-stadiums/district";
76
+ * const gates = venueGates("bc-place");
77
+ * console.log(gates.length, gates.filter((g) => g.basis === "documented").map((g) => g.name.en).join(", "));
78
+ * // 8 Gate G
79
+ * ```
80
+ */
81
+ export function venueGates(id) {
82
+ const facts = GATE_FACTS[id];
83
+ const outline = outlineOf(id);
84
+ if (facts === undefined || outline === undefined)
85
+ return [];
86
+ const ring = metresFrom(outline.centre, outline.ring);
87
+ const gates = [];
88
+ for (const fact of facts) {
89
+ const p = hit(ring, fact.bearing);
90
+ if (p === undefined)
91
+ continue;
92
+ gates.push({ key: `${id}:${fact.id}`, venue: id, id: fact.id, name: { en: gateName(fact, "en"), ja: gateName(fact, "ja") }, at: lonLatFrom(outline.centre, p).map((x) => Math.round(x * 1e6) / 1e6), bearing: fact.bearing, basis: fact.basis, read: fact.read });
93
+ }
94
+ return gates;
95
+ }
package/dist/halls.d.ts CHANGED
@@ -61,5 +61,7 @@ export interface Hall {
61
61
  ja: string;
62
62
  };
63
63
  }
64
+ /** The Kodokan's main dojo as drawn: a square floor of 420 jō's area, four contest areas, the seat of honour, the banner, the wood and the gallery, in the two looks the owner's photographs show. The years of both are unknown. */
65
+ export declare const KODOKAN_MAIN: DojoSpec;
64
66
  /** The halls, in the order of their surfaces' fame: the Kodokan, the Budokan, the Kokugikan, the Butokuden and the Budokwai. */
65
67
  export declare const HALLS: readonly Hall[];
package/dist/halls.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** The Kodokan's main dojo as drawn: a square floor of 420 jō's area, four contest areas, the seat of honour, the banner, the wood and the gallery, in the two looks the owner's photographs show. The years of both are unknown. */
2
- const KODOKAN_MAIN = {
2
+ export const KODOKAN_MAIN = {
3
3
  mats: { width: 26.4, length: 26.4 },
4
4
  surround: 4,
5
5
  areas: { across: 2, down: 2 },
@@ -0,0 +1,28 @@
1
+ import type { DistrictMember } from "./district.ts";
2
+ import type { DojoSpec, Language } from "./types.ts";
3
+ /** The main dojo as the district draws it: the atlas's floor and fittings, with no wood round the mats (the 4 metres the atlas draws would not stand inside the real building). */
4
+ export declare const KODOKAN_DOJO: DojoSpec;
5
+ /** How high the room stands, floor to the top of its lid, in metres: the 3D build's own figure (walls of 4.5 metres, the gallery level with their tops, the lid over it), two storeys of the building. Ours. */
6
+ export declare const KODOKAN_ROOM_HEIGHT = 11.2;
7
+ /** The height of the main dojo's floor above the ground, in metres: the 7th floor, six storeys up at the building's own storey height (its height over its storeys, 5.4 metres), and no higher than leaves the room under the roof. */
8
+ export declare const KODOKAN_FLOOR_HEIGHT: number;
9
+ /** Where the Kodokan's building stands and what it is: for tests and pages that say it. */
10
+ export declare const KODOKAN: {
11
+ readonly floorHeight: number;
12
+ readonly roomHeight: 11.2;
13
+ readonly mats: 420;
14
+ readonly seats: 900;
15
+ readonly source: "bldg_ec4aa7a9-f8cb-4abb-af0c-a2cef6d06dd8";
16
+ readonly read: "2026-10-22";
17
+ readonly ring: readonly [readonly [139.753281, 35.707485], readonly [139.753287, 35.707464], readonly [139.753659, 35.707456], readonly [139.753548, 35.707846], readonly [139.753502, 35.707837], readonly [139.753496, 35.707859], readonly [139.753195, 35.707801], readonly [139.753268, 35.707546], readonly [139.753193, 35.707532], readonly [139.75321, 35.707472]];
18
+ readonly height: 43.1;
19
+ readonly storeys: 8;
20
+ readonly room: {
21
+ readonly centre: readonly [139.75342, 35.707654];
22
+ readonly heading: 77;
23
+ readonly side: 31.8;
24
+ readonly clearance: 0.18;
25
+ };
26
+ };
27
+ /** The Kodokan as a member of a district. */
28
+ export declare function kodokanMember(language: Language): DistrictMember;
@@ -0,0 +1,18 @@
1
+ /** The Kodokan's building and the place of its main dojo in it. */
2
+ export declare const KODOKAN_BUILDING: {
3
+ /** The building's id in PLATEAU's file (bldg:Building gml:id). */
4
+ readonly source: "bldg_ec4aa7a9-f8cb-4abb-af0c-a2cef6d06dd8";
5
+ readonly read: "2026-10-22";
6
+ /** The roof-edge outline, `[longitude, latitude]`. */
7
+ readonly ring: readonly [readonly [139.753281, 35.707485], readonly [139.753287, 35.707464], readonly [139.753659, 35.707456], readonly [139.753548, 35.707846], readonly [139.753502, 35.707837], readonly [139.753496, 35.707859], readonly [139.753195, 35.707801], readonly [139.753268, 35.707546], readonly [139.753193, 35.707532], readonly [139.75321, 35.707472]];
8
+ /** The height of the building's top above its ground, in metres, and its storeys above ground. */
9
+ readonly height: 43.1;
10
+ readonly storeys: 8;
11
+ /** The middle of the main dojo's room, `[longitude, latitude]`, the compass heading of its x axis (0 to under 180) and the length of its side in metres, walls and gallery included. */
12
+ readonly room: {
13
+ readonly centre: readonly [139.75342, 35.707654];
14
+ readonly heading: 77;
15
+ readonly side: 31.8;
16
+ readonly clearance: 0.18;
17
+ };
18
+ };
@@ -0,0 +1,18 @@
1
+ // Made by scripts/build-plateau.mjs from data-sources/plateau/2026-10-22/: do not edit by hand.
2
+ //
3
+ // THIS FILE IS DATA FROM PROJECT PLATEAU, edited: the Kodokan's building (its roof-edge outline simplified to 0.3 metres, its height and its storeys, from MLIT's 3D city model of Bunkyo ward, 2025 edition) and where the main dojo's
4
+ // room is put in it (worked out here from the outline, ours). The credit is `PLATEAU_ATTRIBUTION` (plateau.credit.ts), the terms the PLATEAU Site Policy's (free, commercial use included, compatible with CC BY 4.0). Apart from the
5
+ // OpenStreetMap data (ODbL 1.0); nothing is copied from one to the other. The raw extract is in data-sources/plateau/.
6
+ /** The Kodokan's building and the place of its main dojo in it. */
7
+ export const KODOKAN_BUILDING = {
8
+ /** The building's id in PLATEAU's file (bldg:Building gml:id). */
9
+ "source": "bldg_ec4aa7a9-f8cb-4abb-af0c-a2cef6d06dd8",
10
+ "read": "2026-10-22",
11
+ /** The roof-edge outline, `[longitude, latitude]`. */
12
+ "ring": [[139.753281, 35.707485], [139.753287, 35.707464], [139.753659, 35.707456], [139.753548, 35.707846], [139.753502, 35.707837], [139.753496, 35.707859], [139.753195, 35.707801], [139.753268, 35.707546], [139.753193, 35.707532], [139.75321, 35.707472]],
13
+ /** The height of the building's top above its ground, in metres, and its storeys above ground. */
14
+ "height": 43.1,
15
+ "storeys": 8,
16
+ /** The middle of the main dojo's room, `[longitude, latitude]`, the compass heading of its x axis (0 to under 180) and the length of its side in metres, walls and gallery included. */
17
+ "room": { "centre": [139.75342, 35.707654], "heading": 77, "side": 31.8, "clearance": 0.18 },
18
+ };
@@ -0,0 +1,34 @@
1
+ // The Kodokan (講道館) as a venue of a district: its building, from Project PLATEAU, and its main dojo (大道場), the room on the 7th floor with its gallery on the 8th, drawn in the air on its real floor over the building's own outline.
2
+ // The facts that are documented are the count (420 jō of mats, four contest areas at once), the floor (the 7th), the gallery (the 8th, about 900) and the building (8 floors and a basement; PLATEAU: 8 storeys above ground, 43.1 metres): the Kodokan's own page, English
3
+ // Wikipedia and the IJF say the first four (`docs/district.md`, "The Kodokan"). Ours: the shape of the floor (no source gives it: a square of 26.4 metres, the area of 420 jō, on which four areas of 8 metres fit, as `halls.ts` draws it), the room's place in the
4
+ // building (the square that stands farthest from its walls, `scripts/build-plateau.mjs`), the wood round the mats (none, so that the room fits the real outline) and the height of the 7th floor (six storeys of the building's own). Pure data and a few sums.
5
+ import { FEET_PER_METRE } from "./geometry.js";
6
+ import { KODOKAN_MAIN } from "./halls.js";
7
+ import { KODOKAN_BUILDING as BUILDING } from "./kodokan.data.js";
8
+ import { PLATEAU_ATTRIBUTION } from "./plateau.credit.js";
9
+ /** The main dojo as the district draws it: the atlas's floor and fittings, with no wood round the mats (the 4 metres the atlas draws would not stand inside the real building). */
10
+ export const KODOKAN_DOJO = { ...KODOKAN_MAIN, surround: 0 };
11
+ /** How high the room stands, floor to the top of its lid, in metres: the 3D build's own figure (walls of 4.5 metres, the gallery level with their tops, the lid over it), two storeys of the building. Ours. */
12
+ export const KODOKAN_ROOM_HEIGHT = 11.2;
13
+ /** The height of the main dojo's floor above the ground, in metres: the 7th floor, six storeys up at the building's own storey height (its height over its storeys, 5.4 metres), and no higher than leaves the room under the roof. */
14
+ export const KODOKAN_FLOOR_HEIGHT = Math.min((6 * BUILDING.height) / BUILDING.storeys, BUILDING.height - KODOKAN_ROOM_HEIGHT);
15
+ /** Where the Kodokan's building stands and what it is: for tests and pages that say it. */
16
+ export const KODOKAN = { ...BUILDING, floorHeight: KODOKAN_FLOOR_HEIGHT, roomHeight: KODOKAN_ROOM_HEIGHT, mats: 420, seats: 900 };
17
+ /** The Kodokan as a member of a district. */
18
+ export function kodokanMember(language) {
19
+ return {
20
+ key: "kodokan",
21
+ id: "kodokan",
22
+ kind: "hall",
23
+ name: { en: "Kodokan", ja: "講道館" },
24
+ ring: BUILDING.ring,
25
+ centre: BUILDING.room.centre,
26
+ heading: BUILDING.room.heading,
27
+ basis: "building",
28
+ seats: KODOKAN.seats,
29
+ options: { sport: "dojo", dojo: KODOKAN_DOJO, crowd: { real: true }, language },
30
+ elevation: KODOKAN_FLOOR_HEIGHT * FEET_PER_METRE,
31
+ shell: { height: BUILDING.height * FEET_PER_METRE, storeys: BUILDING.storeys },
32
+ attribution: PLATEAU_ATTRIBUTION,
33
+ };
34
+ }
@@ -0,0 +1,2 @@
1
+ export { OSM_AREAS, OSM_ATTRIBUTION, VANCOUVER_CENTRE } from "./osm.context.data.ts";
2
+ export type { ContextArea } from "./context.ts";
@@ -0,0 +1,2 @@
1
+ // The buildings and streets round a district, from OpenStreetMap (ODbL 1.0), as data a district takes in its `context` option: downtown Vancouver's, round BC Place and Rogers Arena, with the credit that goes with a drawing made from it.
2
+ export { OSM_AREAS, OSM_ATTRIBUTION, VANCOUVER_CENTRE } from "./osm.context.data.js";
@@ -0,0 +1,10 @@
1
+ import type { ContextArea } from "./context.ts";
2
+ /** The credit that goes with a drawing made from these buildings and streets, in each language. */
3
+ export declare const OSM_ATTRIBUTION: {
4
+ en: string;
5
+ ja: string;
6
+ };
7
+ /** The buildings and streets round each district that has a mask, by key. */
8
+ export declare const OSM_AREAS: Record<string, ContextArea>;
9
+ /** Downtown Vancouver round BC Place and Rogers Arena. */
10
+ export declare const VANCOUVER_CENTRE: ContextArea;