@johnmorrisdotca/kyogijo-stadiums 0.40.2 → 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 (61) hide show
  1. package/CHANGELOG.md +56 -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 +9 -0
  7. package/dist/district-entry.d.ts +14 -0
  8. package/dist/district-entry.js +9 -0
  9. package/dist/district.d.ts +54 -1
  10. package/dist/district.dom.d.ts +31 -0
  11. package/dist/district.dom.js +52 -0
  12. package/dist/district.js +71 -11
  13. package/dist/district.turn.js +30 -6
  14. package/dist/gates.d.ts +56 -0
  15. package/dist/gates.js +95 -0
  16. package/dist/halls.d.ts +2 -0
  17. package/dist/halls.js +1 -1
  18. package/dist/kodokan.d.ts +28 -0
  19. package/dist/kodokan.data.d.ts +18 -0
  20. package/dist/kodokan.data.js +18 -0
  21. package/dist/kodokan.js +34 -0
  22. package/dist/osm-entry.d.ts +2 -0
  23. package/dist/osm-entry.js +2 -0
  24. package/dist/osm.context.data.d.ts +10 -0
  25. package/dist/osm.context.data.js +1107 -0
  26. package/dist/outside.d.ts +43 -0
  27. package/dist/outside.dom.d.ts +44 -0
  28. package/dist/outside.dom.js +203 -0
  29. package/dist/outside.js +227 -0
  30. package/dist/outside.paint.d.ts +57 -0
  31. package/dist/outside.paint.js +88 -0
  32. package/dist/plateau-entry.d.ts +3 -0
  33. package/dist/plateau-entry.js +3 -0
  34. package/dist/plateau.credit.d.ts +5 -0
  35. package/dist/plateau.credit.js +7 -0
  36. package/dist/plateau.data.d.ts +7 -0
  37. package/dist/plateau.data.js +757 -0
  38. package/dist/stadiums.d.ts +14 -0
  39. package/dist/stadiums.data.js +2 -1
  40. package/dist/stadiums.js +2 -0
  41. package/dist/strings.d.ts +3 -1
  42. package/dist/strings.js +4 -4
  43. package/dist/turn.dom.d.ts +10 -0
  44. package/dist/turn.dom.js +21 -0
  45. package/dist/turn.kit.d.ts +10 -3
  46. package/dist/turn.kit.js +7 -0
  47. package/dist/turn.markup.js +7 -2
  48. package/dist/turn.model.d.ts +3 -0
  49. package/dist/turn.style.js +14 -3
  50. package/dist/venue.real.d.ts +22 -1
  51. package/dist/venue.real.js +76 -7
  52. package/dist/venues.d.ts +9 -1
  53. package/dist/venues.data.js +2 -1
  54. package/dist/venues.js +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/walk.d.ts +56 -0
  58. package/dist/walk.data.d.ts +4 -0
  59. package/dist/walk.data.js +1295 -0
  60. package/dist/walk.js +120 -0
  61. package/package.json +17 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.43.0] - 2026-10-22
11
+
12
+ ### Fixed
13
+
14
+ - **A see-through building's walls now fade by the side the eye is on, at every turn and tilt** (`Plane.fadeSide`): the Kodokan's near walls gave way only when the eye was low, and at some angles and tilts a near wall was drawn opaque over the dojo. Every wall between the eye and the room is now clear, a wall on the far side stays whole behind it, and a test turns the view in steps of 15 degrees at six tilts and finds no wall over the dojo's floor that is not clear. The roof is clearer too.
15
+ - **The district page's seven presets pushed the crowd bar off the first screen on a phone** (the new preset made the chips wrap into one more row): on a narrow screen the chips are 44 pixels high with a smaller gap, so that the bar and the plan stay in view.
16
+ - **Auto-turn could not be kept stopped on the district, stadiums, venues and halls pages**: pressing the button to stop the turn worked, but the pointer coming back over the view turned it again by hover. A reader who has used the Auto-turn button (or the Space key) is left alone by the pointer for the rest of the page; the button toggles every time. A new test presses it on each page, with and without yaw, pitch and zoom in the address, by mouse, keyboard and tap.
17
+
18
+ ### Added
19
+
20
+ - **The Kodokan in the Tokyo district** ([the district page](./docs/district.md#the-kodokan-the-great-dojo-in-the-air)): `"kodokan"` is a venue of a district. Its main dojo (the great dojo, on the 7th floor, 420 mats, four contest areas at once, a gallery of about 900 on the 8th) is drawn in the air on its real floor, 31.9 metres up, in Project PLATEAU's building (8 storeys, 43.1 metres, its real outline), with the package's judo dojo inside: the four areas, the gallery, the seat of honour. No source gives the floor's shape or the room's place in the building: the floor is a square of 420 jō and the room is put where it stands farthest from the walls (ours, and said). `building: "xray" | "solid" | "none"` draws the building with walls that give way to the eye as it looks in (the default), whole, or not at all. The building is the venue's own, so the surrounding `context` leaves its copy out. A district with the Kodokan carries PLATEAU's credit without any context. New in the Tokyo district of the demo: a Kodokan choice, a Kodokan building choice kept in the address (`building=solid`, `building=none`), and a preset "Tokyo Dome City and the Kodokan". A picture of the dojo in the docs.
21
+ - A member of a district can stand on a floor above the ground (`DistrictMember.elevation`, in feet): its planes are lifted, its floor is drawn in the air as a plane of its own and the ground under it is left bare in 3D.
22
+ - **Downtown Vancouver among its streets** ([the page](./docs/district.md#among-the-streets-of-downtown-vancouver)): `context: [VANCOUVER_CENTRE]` (new entry `@johnmorrisdotca/kyogijo-stadiums/osm`) draws the carriageways, viaducts, SkyTrain guideway, sidewalks and paths flat under BC Place and Rogers Arena, and the buildings round them flat and, for the biggest 130, standing in 3D, with the heights OpenStreetMap maps (else its levels, else 10 metres, ours). The district's `context` takes a `streets` layer beside the `buildings` one and a `landmark` height of its own. OpenStreetMap, ODbL 1.0, kept apart from the PLATEAU data of Tokyo; `pnpm osm-context` makes it from the raw answer the walkable mask is made from.
23
+ - **BC Place's 36 masts**, each 47.5 metres over the rim and leaning out, two crossed strips to a mast (STRUCTURE magazine's count and height, cited; the lean is ours), and the walls in two bands, a podium, a ledge and glazing, for BC Place and Rogers Arena (ours).
24
+ - **Rogers Arena's roof closed in the middle by a cap with a long skylight** along the rink (ours, from the brief).
25
+ - **The gates of a venue** ([the page](./docs/gates.md)): `venueGates("bc-place")` gives eight gates, one of them Gate G on the south side (Wikipedia's), and `venueGates("rogers-arena")` four on the Pat Quinn Way side and at the north-west, each a point on the building's outline with the way it faces, a name in English and Japanese and how it is known (`documented` or `plausible`); `gates: true` on a district marks them on the plan and the 3D floor. The data the crowd's walk in and out will use for its queues.
26
+ - **The district page** has Surrounding buildings and streets for Vancouver (on by default), Retractable roof open (3D, for BC Place and T-Mobile Park, kept in the address as `roofopen=1`) and Gates (`gates=1`).
27
+
28
+ ### Changed
29
+
30
+ - **The demo's browser tests run in four shards in CI** (`demo (1/4)` to `demo (4/4)`, each building the demo and playing a quarter of the tests in the three projects), and a test that fails on the runner is played once more (`retries: 1` in CI only, the report saying "flaky" for one that passed the second time): the job took 45 to 95 minutes on one runner.
31
+
32
+ ## [0.42.1] - 2026-10-22
33
+
34
+ Not published on its own; shipped in 0.43.0.
35
+
36
+ ### Fixed
37
+
38
+ - The README no longer carries the grey "Renamed" box about the package's old name (the 0.4.0 entry below keeps the history).
39
+ - **The demo's 3D views keep the camera when the choice changes** (the district, stadiums, venues and layouts pages): another venue or district, another stadium or configuration, another layout, the roof, or the flat view and back used to open the view afresh on its viewpoint, taking away the turn, tilt and zoom the reader had chosen and stopping the auto-turn. The view is now opened where the last one was (turn, tilt, zoom, and Play if it was turning) and only the frame it fits changes. A turn made by hand is in the address as `yaw`, `pitch` and `zoom` (under the home page's `f` prefix for its featured arena), and an address that names them opens on them; a viewpoint chosen from the list or the view's own buttons takes them out again and says itself. A box that is not the viewpoint (the boards, the labels, the language) no longer sends the view back to the viewpoint, and the district and stadiums pages now write `viewpoint=` for every viewpoint but their own default, overhead (a reload used to lose a chosen low corner).
40
+
41
+ ## [0.42.0] - 2026-10-22
42
+
43
+ Not published on its own; shipped in 0.43.0.
44
+
45
+ ### Added
46
+
47
+ - **A district among its buildings, from Project PLATEAU** ([the district page](./docs/district.md#among-its-buildings-project-plateau)): `drawDistrict`, `planDistrict` and `buildDistrictTurn` take `context: [TOKYO_DOME_CITY]` (new entry `@johnmorrisdotca/kyogijo-stadiums/plateau`), the 734 buildings within 500 metres of the middle of Tokyo Dome City from the Ministry of Land, Infrastructure, Transport and Tourism's 3D city model of Bunkyo ward (2025 edition). They lie flat on the floor in four tones by height, and the 130 biggest and tallest stand in the 3D view at their real heights, walls lit by the way they face and hip roofs where the model has them: Tokyo Dome Hotel at 155 metres, Bunkyo Civic Center at about 139. The frame takes in the streets round the venues (150 metres) and grows to any landmark of 100 metres or more within 400. A building that is a venue's own (the Dome's stands and roof) is left out; the building Korakuen Hall stands in is a ghost. The data is cut down at build time (`pnpm plateau:fetch` reads the ward's CityGML by range requests, `pnpm plateau` writes `src/plateau.data.ts`: 40 KB, 16 KB gzipped) and nothing is read at run time; the raw fields are in `data-sources/plateau/2026-10-22/`. A district that does not ask is drawn as before and carries none of it. The credit (PLATEAU's site policy: free, commercial use included, compatible with CC BY 4.0; credited, and said to be edited) is in each description in English and in Japanese and in `NOTICE.md`. The data file is one frame with separate layers (`ContextArea.layers`), so trees and land cover can be laid beside the buildings.
48
+ - **A Surrounding buildings choice on the district page**, on for Tokyo Dome City (`context=0` in the address turns it off), and a 3D picture of the district among its buildings in the docs.
49
+ - The three links John sent (jeantimex/tokyo, mkturkcan/valdrada and a Tokyo Tower model on Printables) in [the references](./docs/references.md), with what each may be used for.
50
+
51
+ ### Changed
52
+
53
+ - A plane of the 3D view can belong to no building (`APART`): the view stacks such planes by depth among themselves and weaves them into the buildings' (which keep their building-by-building order), so a district's surrounding blocks need no group of their own.
54
+
55
+ ## [0.41.0] - 2026-10-22
56
+
57
+ Not published on its own; shipped in 0.42.0.
58
+
59
+ ### Added
60
+
61
+ - **The crowd outside the venue** ([the page](./docs/outside.md)): `outside: { level: 0 | 25 | 50 | 75 | 100 }` on a [district](./docs/district.md) puts that share of the venues' seats' worth of people on the sidewalks, plazas, parks, platforms and grounds round them, in 2D (`mountDistrict`, new) and in the 3D turn (`mountDistrictTurn`, whose view has `setOutside(level)` and `outsideCost()`), never on a carriageway, a railway, water or a building. The crowd is one ordered list, so a level is a prefix of it (nobody moves when the level does), each venue's share is exact to a person at every level and the same options give the same list (`outsideCrowd`, `outsideCount`). It is drawn with no element for a person: two typed arrays, one loop and one `putImageData` on one canvas, laid over the plan or on the 3D floor, where the floor's own matrix carries it round; a district seen whole shows density and a near view shows each person, and nothing is painted again when the view turns. Measured at 100 percent for BC Place and Rogers Arena together (73,371 people): a paint of 2 to 17 ms on a level change (the first about twice that), 0.3 ms more of the main thread a frame while the view turns, 60 frames a second with it as without, and 250 ms once for the mask and the crowd.
62
+ - **The walkable mask of Vancouver** (BC Place and Rogers Arena, one mask shared): made from OpenStreetMap's own answer (Overpass, ODbL 1.0, kept raw in `data-sources/walk/2026-10-22/`, credited "(c) OpenStreetMap contributors") by `pnpm walk:fetch` and `pnpm walk` (`scripts/walk-rules.mjs` says what a person may stand on and what stops a crowd) into `src/walk.data.ts`, apart from the CC0 data. `src/outside.raw.test.js` holds all 73,371 people to the raw answer. Only Vancouver has a mask; every other venue comes in a later release, and so do the entrances and the walk in and out.
63
+ - **A bar of the five levels** on the demo's [district page](https://johnmorrisdotca.github.io/kyogijo-stadiums/district.html?outside=50), over the plan and the 3D view, kept in the address (`outside=50`), in English and Japanese, with the number of people said in words.
64
+ - `pnpm outside:cost` prints the cost of a crowd; `DistrictPlan.origin` is the point the plan's metres are measured from.
65
+
10
66
  ## [0.40.2] - 2026-10-21
11
67
 
12
68
  ### Fixed
package/NOTICE.md CHANGED
@@ -47,6 +47,9 @@ picture or code of the sources is copied; each source is named here so that the
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
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, Koshien Stadium), 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 the heading and home plate at which its published fence lies nearest the mapped grass of the field (Koshien Stadium), 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
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 three 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
+ | `walk`: the ground a crowd outside a district's venues may stand on and what stops it (`WALK_DATA`, `src/walk.data.ts`): paths and sidewalks, plazas, squares, parks and platforms, and carriageways, cycle tracks, railways, water and buildings, as lines with a width and as rings | OpenStreetMap, through the Overpass API: every way and relation of the highways, buildings, railways, water, squares, parks and platforms within 350 metres of the district's venues, read on 2026-10-22, with the raw answer kept in `data-sources/walk/2026-10-22/`; laid flat in metres, simplified to 30 centimetres and written in quarter-metres, by the rules of `scripts/walk-rules.mjs` (no name, id or tag is copied) | 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 crowd or a drawing made from it is a produced work and carries the credit (`VENUE_ATTRIBUTION`) |
51
+ | `plateau`: the buildings round Tokyo Dome City (`TOKYO_DOME_CITY`, `src/plateau.data.ts`): each building's roof-edge outline simplified to 0.6 metres on a half-metre grid, its height and, for a pitched LOD2 roof, the rise of the roof | Project PLATEAU, the Ministry of Land, Infrastructure, Transport and Tourism's (MLIT) 3D city model of Bunkyo ward, 2025 edition (product specification 5.0, CityGML, https://www.geospatial.jp/ckan/dataset/plateau-13105-bunkyo-ku-2025), read on 2026-10-22: the buildings within 500 metres of the middle of the district, with the raw fields kept in `data-sources/plateau/2026-10-22/` (`pnpm plateau:fetch`, `pnpm plateau`) | PLATEAU Site Policy (https://www.mlit.go.jp/plateau/site-policy/), clause 3: free use, commercial use included, compatible with CC BY 4.0 and the Public Data License 1.0; the source is credited and the data says it was edited and not made by MLIT (`PLATEAU_ATTRIBUTION`: "Project PLATEAU, Ministry of Land, Infrastructure, Transport and Tourism (MLIT), https://www.mlit.go.jp/plateau/ (3D city model of Bunkyo ward, 2025 edition), simplified and edited by Kyogijo Stadiums; not made by MLIT"); the copyright of a ward's data belongs to its local government. Kept in its own file, apart from the OpenStreetMap data (ODbL) and the CC0 files |
52
+ | `district`: the Kodokan (`KODOKAN`, `src/kodokan.ts`, `src/kodokan.data.ts`): its building's outline (simplified to 0.3 metres), height (43.1 metres) and storeys (8), and where the main dojo's room lies in it | The building is Project PLATEAU's, as the `plateau` row above (the same credit, `PLATEAU_ATTRIBUTION`; the building's id in the file is in `src/kodokan.data.ts`); OpenStreetMap's way 170598646 (8 levels) was read on 2026-10-22 as a check that it is the same building, and nothing of it is copied. The main dojo is on the 7th floor with 420 mats, four contests at once, a gallery of about 900 on the 8th, in a building of eight floors and a basement: the Kodokan's own page (facilities), English Wikipedia (the article as last edited on 2026-04-20) and the IJF's article "Kodokan: an institution, a legendary place and a training venue", read on 2026-10-22; facts, no text copied | PLATEAU's terms as above for the building; facts for the rest. The shape of the floor, the room's place in the building, the wood (none) and the height of the 7th floor are ours, and the pages say so |
50
53
  | `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
54
  | `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
55
  | `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/) |
@@ -463,6 +466,20 @@ BC Place, Lumen Field, T-Mobile Park, Tokyo Dome, Korakuen Hall and Koshien Stad
463
466
  | Korakuen Hall | Q4389857 | way/45977418 | Korakuen Hall, 1372211318 |
464
467
  | Koshien Stadium | Q959811 | relation/9845926 | Koshien Stadium, 1374217766 |
465
468
 
469
+ ## The context's and the gates' sources
470
+
471
+ Downtown Vancouver's buildings and streets (`VANCOUVER_CENTRE`, `src/osm.context.data.ts`) are OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, made by `pnpm osm-context` from the same raw answer as the walkable mask (`data-sources/walk/2026-10-22/osm-vancouver.json`): each building's outline simplified to 60 centimetres with the height OpenStreetMap maps (else its levels at 3.2 metres, else 10 metres, which is ours) and each street as a line with a width; no name, id or tag is copied. It is kept in its own file, apart from every CC0 and CC BY-SA file and from the PLATEAU data of Tokyo.
472
+
473
+ BC Place's masts (`scripts/stadiums.data.json`, `roof.masts`: 36, 47.5 metres) are a fact read from STRUCTURE magazine's article on the roof (https://www.structuremag.org/article/bc-place-revitalization/, 2026-10-22); its words are not kept. The gates (`src/gates.ts`) are ours, placed on OpenStreetMap's outlines; the one fact is that BC Place has a Gate G on the south side, from the English Wikipedia article (revision 1372065649, CC BY-SA 4.0, our own words).
474
+
475
+ ## The walkable masks' sources
476
+
477
+ The ground round the venues of a district, read on 2026-10-22 (`pnpm walk:fetch`; the file is `data-sources/walk/2026-10-22/osm-<district>.json`, as Overpass returned it, with the query and the time of OpenStreetMap's data) and made into `src/walk.data.ts` by `pnpm walk`. It is OpenStreetMap's, ODbL 1.0, (c) OpenStreetMap contributors, and lives in that file alone. Nothing of Wikidata or Wikipedia is in it.
478
+
479
+ | District | Venues | Radius | OpenStreetMap data of |
480
+ | --- | --- | --- | --- |
481
+ | `vancouver` | BC Place, Rogers Arena | 350 metres | 2026-10-22T07:58:28Z |
482
+
466
483
  ## The World Cups' articles
467
484
 
468
485
  The grounds each World Cup used, and where its final was played, were read from the venue section of the tournament's English Wikipedia article at the revision named (32 tournaments), by the same address. FIFA's own pages are not used.
package/README.md CHANGED
@@ -13,8 +13,6 @@ The surfaces of forty-three sports, from football and curling to the budō and t
13
13
 
14
14
  <p align="center"><a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/"><strong>Open the demo →</strong></a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/">Documentation</a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/docs/gallery.html">Gallery</a> · <a href="./docs/compare.md">How it compares</a> · <a href="https://johnmorrisdotca.github.io/kyogijo-stadiums/api.html">API reference</a></p>
15
15
 
16
- > **Renamed.** Previously `@johnmorrisdotca/kyogijo`; renamed in 0.4.0 to `@johnmorrisdotca/kyogijo-stadiums`. The product is still Kyogijo Stadiums (競技場); only the package and the repository names changed. See the [changelog](./CHANGELOG.md).
17
-
18
16
  <table align="center">
19
17
  <tr>
20
18
  <td align="center" valign="top">
@@ -163,7 +161,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
163
161
  | `./geometry`, `./frames`, `./layers`, `./charts`, `./replay` | The dimensions in any unit; the providers' frames; data layers; charts; tracking data played on a surface. |
164
162
  | `./athletics`, `./budo`, `./skating` | The athletics stadium, the budō (areas, dohyō, kyūdōjō, the dojo builder `drawDojo`, the 82 kimarite) and the skating rinks: plans, races and charts. |
165
163
  | `./parks`, `./grounds`, `./arenas`, `./atlas`, `./eras`, `./leagues`, `./events` | The ballparks, grounds, arenas, every place, a season's markings, each league's rulebook, the special games. |
166
- | `./venues`, `./stadiums`, `./district` | Real arenas and stadiums in their buildings' outlines; districts of venues on one plan. |
164
+ | `./venues`, `./stadiums`, `./district`, `./plateau` | Real arenas and stadiums in their buildings' outlines; districts on one plan, among PLATEAU's buildings. |
167
165
  | `./turn` | A surface as a 3D view that turns, with the net, basket, goal or posts standing on it. |
168
166
  | `./scene`, `./canvas`, `./d3`, `./plot`, `./geojson`, `./png` | The drawing as data, on a canvas, in d3, as an Observable Plot mark, as GeoJSON, as a PNG (the optional peer `@resvg/resvg-js`). |
169
167
  | `./multi`, `./judging`, `./layouts` | `drawMulti` (several surfaces for one event, [docs](./docs/multi.md), [demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html)); `drawJudging` (who judges where, [docs](./docs/judging.md)); `drawGrid` (small multiples, backgrounds). |
@@ -0,0 +1,141 @@
1
+ import { type LonLat } from "./venues.geo.ts";
2
+ import { type Plane, type Upright } from "./turn.kit.ts";
3
+ import type { Pt } from "./crowd.ts";
4
+ /** One layer of an area's data: its rows, and where they came from and under what terms, since a layer has a source of its own (buildings from PLATEAU; trees and land cover from another). */
5
+ export interface ContextLayer {
6
+ /** What it was made from: the dataset and its page. */
7
+ source: string;
8
+ /** The day the source was read. */
9
+ read: string;
10
+ licence: string;
11
+ /** The credit that goes with a drawing made from this layer, in English and in Japanese. */
12
+ attribution: {
13
+ en: string;
14
+ ja: string;
15
+ };
16
+ /** The layer's rows, each a list of whole numbers in grid steps of the area's frame (the row format is the layer's own, said at its name in `ContextArea.layers`). */
17
+ rows: readonly (readonly number[])[];
18
+ }
19
+ /**
20
+ * An area of context round a district: ONE coordinate frame (the origin, the grid step and the radius, in metres east and north of the origin) and separate layers laid in it. Today there is one layer, `buildings`; trees and land
21
+ * cover are added beside it later as layers of their own, each with its own source and licence, with no change to the frame or to the layers that are there. A layer a caller does not ask for or an area lacks is simply not drawn.
22
+ * The row of a `buildings` layer: the height and the rise of the roof (0 for a flat one) in grid steps, then the first point (east, north) and each step to the next, in grid steps from the origin.
23
+ */
24
+ export interface ContextArea {
25
+ key: string;
26
+ name: {
27
+ en: string;
28
+ ja: string;
29
+ };
30
+ /** The middle of the area, `[longitude, latitude]`: the point the rows' coordinates are measured from. */
31
+ origin: readonly [number, number];
32
+ /** How far from the middle the data reaches, in metres. */
33
+ radius: number;
34
+ /** The step the coordinates and the heights are counted in, in metres (a half). */
35
+ grid: number;
36
+ /** A building this tall, in metres, is a landmark that the plan's frame grows to take in; `CONTEXT_LANDMARK` when missing. A city of many tall buildings (Vancouver's downtown) sets it above them, so that the frame stays round the venues. */
37
+ landmark?: number;
38
+ layers: {
39
+ buildings?: ContextLayer;
40
+ streets?: ContextLayer;
41
+ trees?: ContextLayer;
42
+ landCover?: ContextLayer;
43
+ };
44
+ }
45
+ /** One building as the district holds it: its outline in feet in the district's frame (x to the right, y down the page), its height and the rise of its roof in feet. */
46
+ export interface ContextBlock {
47
+ /** A name for the building that is the same on every run: the area's key and its place in the area's list. */
48
+ id: string;
49
+ ring: Pt[];
50
+ /** The height of its top above the ground, in feet. */
51
+ height: number;
52
+ /** How far a pitched roof rises over its eaves, in feet: 0 for a flat roof. */
53
+ rise: number;
54
+ /** Its ground area, in square feet. */
55
+ area: number;
56
+ /** A venue stands in this building (Korakuen Hall's building, which would hide it): it is drawn as a ghost. */
57
+ ghost: boolean;
58
+ /** The middle a see-through building's walls give way from, in feet: the venue raised in it (the Kodokan's main dojo). Set only by `shellBlock`. */
59
+ xray?: Pt;
60
+ /** Whether the building lies wholly inside the plan's frame: only such a building stands up in the 3D view, one the frame cuts is a part of the floor. */
61
+ whole: boolean;
62
+ }
63
+ /** One street of the `streets` layer as the district holds it: its kind (0 a carriageway at street level, 1 a carriageway on a viaduct or a bridge, 2 a railway, 3 a sidewalk or a path), its width in feet and its line in feet in the district's frame. */
64
+ export interface ContextStreet {
65
+ kind: 0 | 1 | 2 | 3;
66
+ width: number;
67
+ points: Pt[];
68
+ }
69
+ /** What a district's context is: the buildings kept, and how many were left out because they are a venue's own. */
70
+ export interface ContextPlan {
71
+ blocks: ContextBlock[];
72
+ /** The streets, from a `streets` layer: drawn flat on the floor, under the buildings. */
73
+ streets?: ContextStreet[];
74
+ /** The buildings of the model that are a venue (the Dome's own parts), left out: the venue draws them. */
75
+ owned: number;
76
+ /** The credits of the areas used, in each language. */
77
+ attribution: {
78
+ en: string[];
79
+ ja: string[];
80
+ };
81
+ }
82
+ /** How many buildings are raised as blocks in the 3D view (the biggest and the tallest of those on the plan): the rest are the floor's own, flat. Every block is a few elements of the page. */
83
+ export declare const CONTEXT_BLOCKS = 130;
84
+ /** Feet of the plan's frame beyond the venues' buildings that a district with context shows, so that the streets round them are in it: 150 metres. */
85
+ export declare const CONTEXT_REACH: number;
86
+ /** A building this tall, in metres, is a landmark: the plan's frame grows to take it in when it stands within `CONTEXT_LANDMARK_REACH` metres of the venues' buildings (Tokyo Dome Hotel, 155 metres, 370 metres from Korakuen Hall). */
87
+ export declare const CONTEXT_LANDMARK = 100;
88
+ export declare const CONTEXT_LANDMARK_REACH = 400;
89
+ /** The heights, in metres, that divide the buildings into four tones: low, middle, high and tall. */
90
+ export declare const CONTEXT_TONES: readonly number[];
91
+ /** A building of an area, decoded: its outline in metres east and north of the area's origin, its height and the rise of its roof in metres. */
92
+ export declare function decodeBuilding(area: Pick<ContextArea, "grid">, row: readonly number[]): {
93
+ ring: Pt[];
94
+ height: number;
95
+ rise: number;
96
+ };
97
+ /** A street of a `streets` layer, decoded: its line in metres east and north of the area's origin. The row is the kind, the width in grid steps, then the first point and each step to the next in grid steps. */
98
+ export declare function decodeStreet(area: Pick<ContextArea, "grid">, row: readonly number[]): Pt[];
99
+ /**
100
+ * The box a district with context is framed to, in feet in the district's frame: the venues' box widened by `CONTEXT_REACH` on every side, and grown to take in every landmark (a building of `CONTEXT_LANDMARK` metres or more) that stands within
101
+ * `CONTEXT_LANDMARK_REACH` metres of the venues' box, with a margin of its own. `box` is the venues' own.
102
+ */
103
+ export declare function contextFrame(areas: readonly ContextArea[], origin: LonLat, turn: number, box: {
104
+ x0: number;
105
+ y0: number;
106
+ x1: number;
107
+ y1: number;
108
+ }): {
109
+ x0: number;
110
+ y0: number;
111
+ x1: number;
112
+ y1: number;
113
+ };
114
+ /** A venue as the context needs it: its building's outline in the district's frame. */
115
+ export interface ContextVenue {
116
+ footprint: readonly Pt[];
117
+ /** The footprint is a building the venue draws itself (the Kodokan's, with its dojo in the air in it): the building of the model that is the same is the venue's, left out. */
118
+ shell?: boolean;
119
+ }
120
+ /**
121
+ * The buildings of some areas placed in a district: turned as the plan is, kept where they stand in the frame, and sorted into the venues' own (a building most of which lies inside a venue's outline is the venue's: it is left out, the
122
+ * venue draws it) and the ghosts (a building that holds a venue inside it). `origin` is the district's own origin (`[longitude, latitude]`), `turn` the degrees the map is turned, `frame` the plan's box in feet.
123
+ */
124
+ export declare function placeContext(areas: readonly ContextArea[], origin: LonLat, turn: number, frame: {
125
+ x: number;
126
+ y: number;
127
+ w: number;
128
+ h: number;
129
+ }, venues: readonly ContextVenue[]): ContextPlan;
130
+ /** The tone (0 low to 3 tall) of a building of `feet` height. */
131
+ export declare const toneOf: (feet: number) => number;
132
+ /** The streets flat on the floor, under the buildings: a path for each kind and width (a foot), widest and lowest kinds first, so that a sidewalk lies over the carriageway beside it and a viaduct's deck over the street under it. */
133
+ export declare function streetsFloor(streets: readonly ContextStreet[]): string;
134
+ /** The buildings flat on the floor of a district: one path for each tone, and one for the ghosts, drawn under the venues. Empty for a district with no context. */
135
+ export declare function contextFloor(plan: ContextPlan): string;
136
+ /** One building as the planes of a block: its walls up to the eaves, and over them a flat roof, or a hip roof where it has a pitched one. A ghost's planes are always mostly cut away, so that a venue inside it shows. */
137
+ export declare function blockPlanes(b: ContextBlock, prefix: string): Plane[];
138
+ /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
139
+ export declare function contextUprights(plan: ContextPlan, most?: number): Upright[];
140
+ /** The building a raised venue stands in as a block: its outline in feet, its height, `xray` the middle its walls give way from (or none for a solid one). */
141
+ export declare function shellBlock(key: string, ring: readonly Pt[], height: number, xray: Pt | undefined): ContextBlock;
@@ -0,0 +1,290 @@
1
+ // The buildings round a district, drawn low in detail so that a district stands in its streets and not on a bare grid: each is the outline of its roof edge, a height and, for some, a pitched roof, cut down at build time from
2
+ // Project PLATEAU's 3D city model (`scripts/build-plateau.mjs`, `plateau.data.ts`). Nothing here reads a tile or makes a request: an area is plain data a caller passes in (`context: [TOKYO_DOME_CITY]`), so a page that does not
3
+ // ask for one carries none. This file places an area's buildings in a district's frame, takes out the ones that are a venue's own (a venue is drawn from its own outline and shell), and draws the rest: flat on the district's
4
+ // floor, shaded by height, and the biggest and tallest as blocks standing on it in the 3D view. Pure: data in, markup and planes out.
5
+ import { FEET_PER_METRE } from "./geometry.js";
6
+ import { boxOf, hullOf, inside, metresFrom } from "./venues.geo.js";
7
+ import { n, shy, wall } from "./turn.kit.js";
8
+ import { pointList, raked } from "./venue.geom.js";
9
+ /** How many buildings are raised as blocks in the 3D view (the biggest and the tallest of those on the plan): the rest are the floor's own, flat. Every block is a few elements of the page. */
10
+ export const CONTEXT_BLOCKS = 130;
11
+ /** Feet of the plan's frame beyond the venues' buildings that a district with context shows, so that the streets round them are in it: 150 metres. */
12
+ export const CONTEXT_REACH = 150 * FEET_PER_METRE;
13
+ /** A building this tall, in metres, is a landmark: the plan's frame grows to take it in when it stands within `CONTEXT_LANDMARK_REACH` metres of the venues' buildings (Tokyo Dome Hotel, 155 metres, 370 metres from Korakuen Hall). */
14
+ export const CONTEXT_LANDMARK = 100;
15
+ export const CONTEXT_LANDMARK_REACH = 400;
16
+ /** The heights, in metres, that divide the buildings into four tones: low, middle, high and tall. */
17
+ export const CONTEXT_TONES = [15, 45, 100];
18
+ const RAD = Math.PI / 180;
19
+ const rotate = ([x, y], degrees) => [x * Math.cos(degrees * RAD) - y * Math.sin(degrees * RAD), x * Math.sin(degrees * RAD) + y * Math.cos(degrees * RAD)];
20
+ const centroid = (ring) => [ring.reduce((s, p) => s + p[0], 0) / ring.length, ring.reduce((s, p) => s + p[1], 0) / ring.length];
21
+ /** The signed area of a ring: positive going clockwise on a page with y down. */
22
+ const signedArea = (ring) => ring.reduce((s, [x, y], i) => s + (x * ring[(i + 1) % ring.length][1] - ring[(i + 1) % ring.length][0] * y), 0) / 2;
23
+ /** A building of an area, decoded: its outline in metres east and north of the area's origin, its height and the rise of its roof in metres. */
24
+ export function decodeBuilding(area, row) {
25
+ const g = area.grid;
26
+ const ring = [];
27
+ let [x, y] = [row[2], row[3]];
28
+ ring.push([x * g, y * g]);
29
+ for (let i = 4; i + 1 < row.length; i += 2) {
30
+ x += row[i];
31
+ y += row[i + 1];
32
+ ring.push([x * g, y * g]);
33
+ }
34
+ return { ring, height: row[0] * g, rise: row[1] * g };
35
+ }
36
+ /** A street of a `streets` layer, decoded: its line in metres east and north of the area's origin. The row is the kind, the width in grid steps, then the first point and each step to the next in grid steps. */
37
+ export function decodeStreet(area, row) {
38
+ const g = area.grid;
39
+ const line = [];
40
+ let [x, y] = [row[2], row[3]];
41
+ line.push([x * g, y * g]);
42
+ for (let i = 4; i + 1 < row.length; i += 2) {
43
+ x += row[i];
44
+ y += row[i + 1];
45
+ line.push([x * g, y * g]);
46
+ }
47
+ return line;
48
+ }
49
+ /**
50
+ * The box a district with context is framed to, in feet in the district's frame: the venues' box widened by `CONTEXT_REACH` on every side, and grown to take in every landmark (a building of `CONTEXT_LANDMARK` metres or more) that stands within
51
+ * `CONTEXT_LANDMARK_REACH` metres of the venues' box, with a margin of its own. `box` is the venues' own.
52
+ */
53
+ export function contextFrame(areas, origin, turn, box) {
54
+ let frame = { x0: box.x0 - CONTEXT_REACH, y0: box.y0 - CONTEXT_REACH, x1: box.x1 + CONTEXT_REACH, y1: box.y1 + CONTEXT_REACH };
55
+ const far = CONTEXT_LANDMARK_REACH * FEET_PER_METRE;
56
+ const near = { x0: box.x0 - far, y0: box.y0 - far, x1: box.x1 + far, y1: box.y1 + far };
57
+ for (const area of areas) {
58
+ const [east, north] = metresFrom(origin, [area.origin])[0];
59
+ for (const row of area.layers.buildings?.rows ?? []) {
60
+ if (row[0] * area.grid < (area.landmark ?? CONTEXT_LANDMARK))
61
+ continue;
62
+ const b = decodeBuilding(area, row);
63
+ const ring = b.ring.map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
64
+ const at = boxOf(ring);
65
+ if (at.x1 < near.x0 || at.x0 > near.x1 || at.y1 < near.y0 || at.y0 > near.y1)
66
+ continue;
67
+ const m = 30 * FEET_PER_METRE;
68
+ frame = { x0: Math.min(frame.x0, at.x0 - m), y0: Math.min(frame.y0, at.y0 - m), x1: Math.max(frame.x1, at.x1 + m), y1: Math.max(frame.y1, at.y1 + m) };
69
+ }
70
+ }
71
+ return frame;
72
+ }
73
+ /**
74
+ * The buildings of some areas placed in a district: turned as the plan is, kept where they stand in the frame, and sorted into the venues' own (a building most of which lies inside a venue's outline is the venue's: it is left out, the
75
+ * venue draws it) and the ghosts (a building that holds a venue inside it). `origin` is the district's own origin (`[longitude, latitude]`), `turn` the degrees the map is turned, `frame` the plan's box in feet.
76
+ */
77
+ export function placeContext(areas, origin, turn, frame, venues) {
78
+ const blocks = [];
79
+ let owned = 0;
80
+ const hulls = venues.map((v) => hullOf([...v.footprint]));
81
+ const [fx0, fy0, fx1, fy1] = [frame.x, frame.y, frame.x + frame.w, frame.y + frame.h];
82
+ for (const area of areas) {
83
+ const [east, north] = metresFrom(origin, [area.origin])[0];
84
+ (area.layers.buildings?.rows ?? []).forEach((row, index) => {
85
+ const b = decodeBuilding(area, row);
86
+ const ring = b.ring.map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
87
+ const box = boxOf(ring);
88
+ // Wholly outside the frame: not drawn at all.
89
+ if (box.x1 < fx0 || box.x0 > fx1 || box.y1 < fy0 || box.y0 > fy1)
90
+ return;
91
+ const middle = centroid(ring);
92
+ const share = (poly) => ring.filter((p) => inside(poly, p)).length / ring.length;
93
+ // A venue inside the building (its middle and at least half of its outline's points): the building is a ghost round it.
94
+ const holds = venues.some((v) => v.shell !== true && inside(ring, centroid(v.footprint)) && v.footprint.filter((p) => inside(ring, p)).length * 2 >= v.footprint.length);
95
+ if (!holds && hulls.some((hull) => inside(hull, middle) || share(hull) >= 0.7)) {
96
+ owned += 1;
97
+ return;
98
+ }
99
+ blocks.push({ id: `${area.key}-${index}`, ring, height: b.height * FEET_PER_METRE, rise: b.rise * FEET_PER_METRE, area: Math.abs(signedArea(ring)), ghost: holds, whole: box.x0 >= fx0 && box.x1 <= fx1 && box.y0 >= fy0 && box.y1 <= fy1 });
100
+ });
101
+ }
102
+ // The streets of the areas: each a line with a width, kept where it reaches into the frame.
103
+ const streets = [];
104
+ for (const area of areas) {
105
+ const [east, north] = metresFrom(origin, [area.origin])[0];
106
+ for (const row of area.layers.streets?.rows ?? []) {
107
+ const points = decodeStreet(area, row).map(([e, nn]) => rotate([(e + east) * FEET_PER_METRE, -(nn + north) * FEET_PER_METRE], turn));
108
+ const box = boxOf(points);
109
+ if (box.x1 < fx0 || box.x0 > fx1 || box.y1 < fy0 || box.y0 > fy1)
110
+ continue;
111
+ streets.push({ kind: row[0], width: row[1] * area.grid * FEET_PER_METRE, points });
112
+ }
113
+ }
114
+ const layers = areas.flatMap((a) => Object.values(a.layers));
115
+ return { blocks, streets, owned, attribution: { en: [...new Set(layers.map((l) => l.attribution.en))], ja: [...new Set(layers.map((l) => l.attribution.ja))] } };
116
+ }
117
+ /** The tone (0 low to 3 tall) of a building of `feet` height. */
118
+ export const toneOf = (feet) => CONTEXT_TONES.filter((m) => feet / FEET_PER_METRE >= m).length;
119
+ const num = (value) => String(Math.round(value * 100) / 100);
120
+ /** The streets flat on the floor, under the buildings: a path for each kind and width (a foot), widest and lowest kinds first, so that a sidewalk lies over the carriageway beside it and a viaduct's deck over the street under it. */
121
+ export function streetsFloor(streets) {
122
+ if (streets.length === 0)
123
+ return "";
124
+ const groups = new Map();
125
+ for (const s of streets) {
126
+ const key = `${s.kind} ${Math.max(1, Math.round(s.width))}`;
127
+ const list = groups.get(key) ?? [];
128
+ list.push(`M${s.points.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}`);
129
+ groups.set(key, list);
130
+ }
131
+ // Under: street-level carriageways, then railways, then sidewalks and paths, then viaducts (which cross over the rest).
132
+ const order = [0, 2, 3, 1];
133
+ const keys = [...groups.keys()].sort((a, b) => order.indexOf(Number(a.split(" ")[0])) - order.indexOf(Number(b.split(" ")[0])) || Number(b.split(" ")[1]) - Number(a.split(" ")[1]));
134
+ return `<g class="ky-dist-streets" aria-hidden="true" fill="none">${keys.map((key) => `<path class="ky-dist-street ky-dist-street-${key.split(" ")[0]}" stroke-width="${key.split(" ")[1]}" d="${groups.get(key).join("")}"/>`).join("")}</g>`;
135
+ }
136
+ /** The buildings flat on the floor of a district: one path for each tone, and one for the ghosts, drawn under the venues. Empty for a district with no context. */
137
+ export function contextFloor(plan) {
138
+ const roads = streetsFloor(plan.streets ?? []);
139
+ if (plan.blocks.length === 0)
140
+ return roads;
141
+ const paths = [[], [], [], [], []];
142
+ for (const b of plan.blocks)
143
+ paths[b.ghost ? 4 : toneOf(b.height)].push(`M${b.ring.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}Z`);
144
+ const classes = ["ky-dist-b0", "ky-dist-b1", "ky-dist-b2", "ky-dist-b3", "ky-dist-ghost"];
145
+ return `${roads}<g class="ky-dist-context" aria-hidden="true">${paths.map((d, i) => (d.length === 0 ? "" : `<path class="ky-dist-block ${classes[i]}" d="${d.join("")}"/>`)).join("")}</g>`;
146
+ }
147
+ // ---- The blocks of the 3D view. ----
148
+ /** How much a building matters to the view: tall and wide first. */
149
+ const weight = (b) => b.height * Math.sqrt(b.area);
150
+ /** The principal axis of a ring, as a unit direction, and the extent of the ring along it and across it, from its middle. */
151
+ function axisOf(ring) {
152
+ const [mx, my] = centroid(ring);
153
+ let [sxx, sxy, syy] = [0, 0, 0];
154
+ for (const [x, y] of ring) {
155
+ sxx += (x - mx) ** 2;
156
+ sxy += (x - mx) * (y - my);
157
+ syy += (y - my) ** 2;
158
+ }
159
+ const angle = 0.5 * Math.atan2(2 * sxy, sxx - syy);
160
+ const along = [Math.cos(angle), Math.sin(angle)];
161
+ const across = [-along[1], along[0]];
162
+ const ts = ring.map(([x, y]) => (x - mx) * along[0] + (y - my) * along[1]);
163
+ const ss = ring.map(([x, y]) => (x - mx) * across[0] + (y - my) * across[1]);
164
+ return { along, t0: Math.min(...ts), t1: Math.max(...ts), s0: Math.min(...ss), s1: Math.max(...ss) };
165
+ }
166
+ /** Whether a ring is convex (either way round). */
167
+ const convex = (ring) => {
168
+ let sign = 0;
169
+ for (let i = 0; i < ring.length; i += 1) {
170
+ const [a, b, c] = [ring[i], ring[(i + 1) % ring.length], ring[(i + 2) % ring.length]];
171
+ const k = (b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0]);
172
+ if (Math.abs(k) < 1e-9)
173
+ continue;
174
+ if (sign === 0)
175
+ sign = Math.sign(k);
176
+ else if (Math.sign(k) !== sign)
177
+ return false;
178
+ }
179
+ return true;
180
+ };
181
+ /** The plane a set of points with heights lies nearest to (least squares), as a slope. */
182
+ function slopeThrough(points) {
183
+ const m = points.length;
184
+ const [mx, my, mz] = [points.reduce((s, p) => s + p[0], 0) / m, points.reduce((s, p) => s + p[1], 0) / m, points.reduce((s, p) => s + p[2], 0) / m];
185
+ let [sxx, sxy, syy, sxz, syz] = [0, 0, 0, 0, 0];
186
+ for (const [x, y, z] of points) {
187
+ sxx += (x - mx) ** 2;
188
+ sxy += (x - mx) * (y - my);
189
+ syy += (y - my) ** 2;
190
+ sxz += (x - mx) * (z - mz);
191
+ syz += (y - my) * (z - mz);
192
+ }
193
+ const det = sxx * syy - sxy * sxy;
194
+ const [gx, gy] = Math.abs(det) < 1e-9 ? [0, 0] : [(sxz * syy - syz * sxy) / det, (syz * sxx - sxz * sxy) / det];
195
+ return { x0: mx, y0: my, z0: mz, gx, gy };
196
+ }
197
+ /** The faces of a hip roof over a ring: from each edge of the ring (at the eaves) up to a ridge along the ring's long axis (at the top). A face is a quad or a triangle. */
198
+ function hipFaces(ring, eave, top) {
199
+ const { along, t0, t1, s0, s1 } = axisOf(ring);
200
+ const [mx, my] = centroid(ring);
201
+ const across = [-along[1], along[0]];
202
+ const half = (s1 - s0) / 2;
203
+ const sm = (s0 + s1) / 2;
204
+ const tm = (t0 + t1) / 2;
205
+ // The ridge runs between t0 + half and t1 - half (a point at the middle where the ring is no longer than it is wide).
206
+ const [r0, r1] = t1 - t0 > 2 * half ? [t0 + half, t1 - half] : [tm, tm];
207
+ const at = (t) => [mx + along[0] * t + across[0] * sm, my + along[1] * t + across[1] * sm];
208
+ const nearest = (p) => at(Math.max(r0, Math.min(r1, (p[0] - mx) * along[0] + (p[1] - my) * along[1])));
209
+ const faces = [];
210
+ ring.forEach((a, i) => {
211
+ const b = ring[(i + 1) % ring.length];
212
+ const [ra, rb] = [nearest(a), nearest(b)];
213
+ const poly = [a, b];
214
+ const heights = [eave, eave];
215
+ if (Math.hypot(rb[0] - b[0], rb[1] - b[1]) > 1e-6) {
216
+ poly.push(rb);
217
+ heights.push(top);
218
+ }
219
+ if (Math.hypot(ra[0] - rb[0], ra[1] - rb[1]) > 1e-6) {
220
+ poly.push(ra);
221
+ heights.push(top);
222
+ }
223
+ if (poly.length >= 3)
224
+ faces.push({ poly, heights });
225
+ });
226
+ return faces;
227
+ }
228
+ /** The light on a wall by the way it faces on the district's plan: three tones, from the side the light comes from (up the page and to the left) to the side in shadow. */
229
+ const sideOf = (a, b, wound) => {
230
+ const [dx, dy] = [b[0] - a[0], b[1] - a[1]];
231
+ const long = Math.hypot(dx, dy) || 1;
232
+ // On a page with y down, the outward normal of a ring wound clockwise is to the left of the walk from a to b, of one wound counter-clockwise to the right.
233
+ const [nx, ny] = wound > 0 ? [dy / long, -dx / long] : [-dy / long, dx / long];
234
+ const lit = nx * -0.45 + ny * -0.55;
235
+ return lit > 0.25 ? 0 : lit > -0.2 ? 1 : 2;
236
+ };
237
+ const SIDES = ["u-block-a", "u-block-b", "u-block-c"];
238
+ /** One building as the planes of a block: its walls up to the eaves, and over them a flat roof, or a hip roof where it has a pitched one. A ghost's planes are always mostly cut away, so that a venue inside it shows. */
239
+ export function blockPlanes(b, prefix) {
240
+ const planes = [];
241
+ const wound = signedArea(b.ring);
242
+ const pitched = b.rise > 3 && b.ring.length <= 8 && convex(b.ring);
243
+ const eave = pitched ? Math.max(b.height - b.rise, 3) : b.height;
244
+ const ghost = (p) => (b.ghost ? { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.16 } : p);
245
+ // A see-through building: each wall gives way as the eye comes round to its side and looks in, to a trace of itself, and the roof is always nearly clear.
246
+ const xray = b.xray;
247
+ const wall1 = (p) => (xray === undefined ? ghost(p) : { ...shy(p, xray), fadeTo: 0.06, fadeSide: true });
248
+ const roof1 = (p) => (xray === undefined ? ghost(p) : { ...p, shy: [0, 0], fadeAt: -10, fadeTo: 0.1 });
249
+ b.ring.forEach((p, i) => {
250
+ const q = b.ring[(i + 1) % b.ring.length];
251
+ const long = Math.hypot(q[0] - p[0], q[1] - p[1]);
252
+ if (long < 3)
253
+ return;
254
+ planes.push(wall1(wall(`${prefix}w${i}`, "block", p[0], p[1], q[0], q[1], 0, eave, `<rect class="u-block ${SIDES[sideOf(p, q, wound)]}" width="${n(long)}" height="${n(eave)}"/>`)));
255
+ });
256
+ const roof = (id, poly, slope, along) => {
257
+ const p = raked(id, "block", poly, slope, along, (corners) => `<polygon class="u-block-roof" points="${pointList(corners)}"/>`);
258
+ if (p !== undefined)
259
+ planes.push(roof1(p));
260
+ };
261
+ if (!pitched) {
262
+ roof(`${prefix}top`, b.ring, { x0: b.ring[0][0], y0: b.ring[0][1], z0: b.height, gx: 0, gy: 0 }, [1, 0]);
263
+ return planes;
264
+ }
265
+ hipFaces(b.ring, eave, b.height).forEach((face, i) => {
266
+ const [p, q] = face.poly;
267
+ const long = Math.hypot(q[0] - p[0], q[1] - p[1]) || 1;
268
+ roof(`${prefix}r${i}`, face.poly, slopeThrough(face.poly.map((pt, k) => [pt[0], pt[1], face.heights[k]])), [(q[0] - p[0]) / long, (q[1] - p[1]) / long]);
269
+ });
270
+ return planes;
271
+ }
272
+ /** The blocks of the 3D view: the `most` buildings that matter most (the tallest and widest, by height times the side of their ground), each a few planes, in the order the plan lists them. */
273
+ export function contextUprights(plan, most = CONTEXT_BLOCKS) {
274
+ const wanted = new Set(plan.blocks.filter((b) => b.whole).sort((a, b) => weight(b) - weight(a) || (a.id < b.id ? -1 : 1)).slice(0, Math.max(0, most)).map((b) => b.id));
275
+ const items = [];
276
+ for (const b of plan.blocks) {
277
+ if (!wanted.has(b.id))
278
+ continue;
279
+ const planes = blockPlanes(b, `c-${b.id}-`);
280
+ if (planes.length === 0)
281
+ continue;
282
+ const [x, y] = centroid(b.ring);
283
+ items.push({ id: `c-${b.id}`, kind: "blocks", at: [x, y], height: b.height, planes });
284
+ }
285
+ return items;
286
+ }
287
+ /** The building a raised venue stands in as a block: its outline in feet, its height, `xray` the middle its walls give way from (or none for a solid one). */
288
+ export function shellBlock(key, ring, height, xray) {
289
+ return { id: `shell-${key}`, ring: [...ring], height, rise: 0, area: Math.abs(signedArea(ring)), ghost: false, whole: true, ...(xray === undefined ? {} : { xray }) };
290
+ }
package/dist/crowd.d.ts CHANGED
@@ -151,6 +151,15 @@ export interface RealShell {
151
151
  rise: number;
152
152
  opening: Pt[];
153
153
  };
154
+ /** The height of the building's podium, a lower band of its walls under the concourse levels, in feet; the walls are drawn in two bands where a venue has one (ours where the sources do not say). */
155
+ podium?: number;
156
+ /** A `fixed` roof closed in the middle by a cap with a long skylight along the surface's long side (Rogers Arena's): ours, from the brief. */
157
+ skylight?: boolean;
158
+ /** A `cable` roof's masts round its rim: how many, and how high they rise over the rim, in feet. */
159
+ masts?: {
160
+ count: number;
161
+ height: number;
162
+ };
154
163
  /** The height of the roof's lowest edge above the surface, in feet. */
155
164
  edge: number;
156
165
  }
@@ -2,4 +2,18 @@ export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE,
2
2
  export type { DistrictMember, DistrictMode, DistrictOptions, DistrictPlaced, DistrictPlan, DistrictVenueInput } from "./district.ts";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.ts";
4
4
  export type { DistrictTurnSettings } from "./district.turn.ts";
5
+ export type { ContextArea } from "./context.ts";
6
+ export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_ROOM_HEIGHT, kodokanMember } from "./kodokan.ts";
5
7
  export { VENUE_ATTRIBUTION } from "./venues.ts";
8
+ export { mountDistrict } from "./district.dom.ts";
9
+ export type { KyogijoDistrict } from "./district.dom.ts";
10
+ export { OUTSIDE_DENSITY, OUTSIDE_LEVELS, OUTSIDE_REACH, OUTSIDE_VENUES, hasOutside, isOutsideLevel, outsideCount, outsideCrowd, outsideLevelOf } from "./outside.ts";
11
+ export type { OutsideCrowd, OutsideLevel, OutsideOptions } from "./outside.ts";
12
+ export { PERSON_METRES, frameOf, rasterOutside } from "./outside.paint.ts";
13
+ export type { OutsideFrame, OutsideStats } from "./outside.paint.ts";
14
+ export { OUTSIDE_ROOM } from "./district.ts";
15
+ export { WALK_CELL, WALK_IDS, walkMask, walkable } from "./walk.ts";
16
+ export type { WalkData, WalkMask } from "./walk.ts";
17
+ export { GATE_FACTS, GATE_VENUES, gateName, venueGates } from "./gates.ts";
18
+ export type { Gate, GateBasis, GateFact } from "./gates.ts";
19
+ export type { DistrictGate } from "./district.ts";
@@ -1,4 +1,13 @@
1
1
  // A district: several real venues of one city on one plan at their real relative positions and orientations (OpenStreetMap, ODbL 1.0), in 2D and in 3D, tight round neighbours or at the real distances of a city.
2
2
  export { DISTRICT_IDS, DISTRICT_MARGIN, DISTRICT_MODES, KYOGIJO_DISTRICT_STYLE, describeDistrict, districtId, districtMember, drawDistrict, isDistrictId, metresText, planDistrict, scaleOf, unknownVenues } from "./district.js";
3
3
  export { buildDistrictTurn, districtPresets, districtTurnMarkup, mountDistrictTurn } from "./district.turn.js";
4
+ export { KODOKAN, KODOKAN_DOJO, KODOKAN_FLOOR_HEIGHT, KODOKAN_ROOM_HEIGHT, kodokanMember } from "./kodokan.js";
4
5
  export { VENUE_ATTRIBUTION } from "./venues.js";
6
+ export { mountDistrict } from "./district.dom.js";
7
+ // The crowd outside the venues, on ground people may stand on (OpenStreetMap, ODbL 1.0), at 0, 25, 50, 75 or 100 percent of the seats.
8
+ export { OUTSIDE_DENSITY, OUTSIDE_LEVELS, OUTSIDE_REACH, OUTSIDE_VENUES, hasOutside, isOutsideLevel, outsideCount, outsideCrowd, outsideLevelOf } from "./outside.js";
9
+ export { PERSON_METRES, frameOf, rasterOutside } from "./outside.paint.js";
10
+ export { OUTSIDE_ROOM } from "./district.js";
11
+ export { WALK_CELL, WALK_IDS, walkMask, walkable } from "./walk.js";
12
+ // The gates of a venue: where the crowd goes in, on the building's outline, with the way each faces and how it is known.
13
+ export { GATE_FACTS, GATE_VENUES, gateName, venueGates } from "./gates.js";