@johnmorrisdotca/kyogijo-stadiums 0.49.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.50.0] - 2026-11-03
11
+
12
+ ### Added
13
+
14
+ - **Circuits: the Las Vegas Strip Circuit (KYOGIJO-STADIUMS-27)** ([circuits](./docs/circuits.md), [demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/circuits.html)): a new entry, `@johnmorrisdotca/kyogijo-stadiums/circuits`, draws a famous race track as OpenStreetMap maps it. `drawCircuit({ circuit: "las-vegas" })` draws the lap as a ribbon of the track's width along the ways of the circuit's relation (joined in the order and the direction they are driven), the pit lane, the circuit's ground and its pit building, the start line and the chequered finish across the track, arrows the way the lap runs, the 17 turns numbered where OpenStreetMap numbers them, a north arrow, a scale bar and the credit, light and dark, in English and Japanese; `circuitTable` gives the facts with their bases. `mountCircuitTurn` and `circuitTurnMarkup` lay it on the 3D view's turning floor with the pit building standing at its mapped 19.5 metres and each turn's number on a post: 68 planes and 292 elements, a floor of 14.8 KB. The facts are Wikidata's and the English and Japanese articles' (6.201 km, 17 turns, counterclockwise, opened 2023-11-16), each `documented` with its revision; the track's width (12 m) and the pit lane's (8 m) are ours, `plausible`, because no source gives them. The drawn lap measures 6,210 m, 0.15 percent over the published length. OpenStreetMap maps no racing kerb, run-off or gravel here, so none is drawn. No mark of a race, a series or a team. `pnpm circuits:fetch` and `pnpm circuits` fetch and build the data (`data-sources/circuits/2026-11-03/`, `src/circuits.data.ts`, `src/circuits.osm.data.ts`, ODbL apart); tests: `src/circuits.test.ts`, `src/circuits.raw.test.js` (the drawing held to the raw answer) and `e2e/circuits.demo.mjs` (the page in 2D and 3D, the Copy button, and the view that does nothing while idle or hidden and grows nothing as it turns). The demo has a circuits page with its code and a Copy button. The docs say how the next circuit (Monaco, Spa, Indianapolis, Suzuka with its crossing) would be added.
15
+
16
+ ## [0.49.1] - 2026-11-02
17
+
18
+ ### Fixed
19
+
20
+ - **A judo tournament layout of the IJF's kata area or the AJJF's contest area now lies where its surface is drawn (KYOGIJO-STADIUMS-21)** ([tournament layouts](./docs/multi.md)): `multiLayout` sized every judo floor by the IJF contest area's figures (4 m of safety and common area), whatever the surface's `league`, while the drawing used the league's own, so a layout of `league: "ijf-kata"` (2 m) or `"ajjf"` (sizes made whole numbers of its 91 cm mats) drew its mats smaller than the floor, its labels and the places for the event's words and marks off the areas, and said it needed more floor than it does (two kata areas need 27 by 15 m, not 33 by 19). The floor is now laid from the judo drawing's own plan (`budoPlan`), and karate's from its league's figures. The presets are unchanged (judo 1 x 3 is 47 by 19 m, 1 x 4 is 61 by 19, karate 2 x 3 is 32 by 22, 2 x 4 is 42 by 22). Test: `src/multi.test.ts` lays the IJF's, the kata and the AJJF's floors at one to three areas and checks the layout's tatami is the drawn outline and the last area ends one safety area short of its edge. No new words.
21
+
22
+ ### Changed
23
+
24
+ - Tests (KYOGIJO-STADIUMS-2): the fingerprint tests no longer break at every version bump. A drawing carries `data-version`, and a drawing with no `id` of its own gets one made from its options and the version, so the hash of the raw text changed with each release and the snapshots, the 90 park hashes and the names fixture were regenerated by hand each time. `src/fingerprint.fixture.ts` hashes the drawing without the version and without those default ids (a page's own id is left alone); twelve test files use it, and the recorded hashes were rewritten once. `src/fingerprint.test.ts` draws the same drawing as version 999.0.0 and checks the fingerprint is the same, the raw hash is not, and a drawing that really changed still changes it. The suite (4,780 tests) also passes with `VERSION` and `package.json` at 0.49.1. The package and its drawings are unchanged.
25
+
26
+ - The demo (not the package): no more folds one under another, and none in every card (KYOGIJO-STADIUMS-18). On the stadiums, district and judging pages the facts, the floors, the choices, the print and the table were open folds in a stack; they are now tabs under the drawing (`demo/tabs.js`, with the arrow keys), the choices first, so the pane and its options share one screen. On the home page the Data and Charts sets each had a "Show the data as a table" fold in every card (thirteen); one switch above each set now swaps its drawings for the tables that stand for them. Tests in `e2e/stadiums.demo.mjs`, `e2e/district.demo.mjs`, `e2e/judging.demo.mjs`, `e2e/data.demo.mjs` and `e2e/charts.demo.mjs`. No new words, so no new Japanese.
27
+
10
28
  ## [0.49.0] - 2026-10-30
11
29
 
12
30
  ### Added
package/NOTICE.md CHANGED
@@ -47,6 +47,8 @@ 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, and from 2026-10-30 Yankee Stadium, Citi Field, MetLife Stadium, the Olympic Stadium of Montreal with the footprint of its tower, and Barclays Center), read on 2026-10-20 and 2026-10-30, with the raw answers kept in `data-sources/stadiums/2026-10-20/` and `data-sources/stadiums/2026-10-30/`; 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
+ | `circuits`: each circuit's lap, pit lane, ground, pit building, turns' numbers, start and finish (`CIRCUIT_OSM`, `src/circuits.osm.data.ts`) | OpenStreetMap, through the Overpass API: the circuit's relation tagged with its Wikidata item (its ways in the order and direction they are driven, its `pit_lane`, its `start` and `finish` nodes and its turns tagged `junction=yes` with a `ref`), the circuit's area and the building inside it, read on 2026-11-03, with the raw answer kept in `data-sources/circuits/2026-11-03/`; the lap is joined and simplified to half a metre | ODbL 1.0, (c) OpenStreetMap contributors; kept apart from every other file and offered under the ODbL with the credit `CIRCUIT_ATTRIBUTION` |
51
+ | `circuits`: each circuit's length, number of turns, direction and opening day (`CIRCUIT_DATA`, `src/circuits.data.ts`) | The item of Wikidata (the labels in English and Japanese, the country P17 and the date of official opening P1619) and the English and Japanese Wikipedia articles of each circuit, at the revisions cited below, in `data-sources/circuits/2026-11-03/` as read; the facts are numbers and words of our own. The track's and the pit lane's widths are ours (`plausible`): no source gives them | Wikidata's data is CC0; the facts read from the articles are facts, with the articles credited below (CC BY-SA 4.0) |
50
52
  | `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
53
  | `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
54
  | `cityctx`: the surroundings of the venues of Seattle (`SEATTLE_SODO`, `SEATTLE_CENTER`, `SEATTLE_CITY`) and the land cover and trees round Vancouver's (`VANCOUVER_NATURE`), in `src/cityctx.data.ts`: buildings (outline, height, for a pitched roof its rise), streets, railways, sidewalks and paths as lines with a width, water, green and wood outlines, and single trees | OpenStreetMap, through the Overpass API: the buildings (with their `height` and `building:levels`), the highways and railways, the tree nodes and rows within the radius of each area, read on 2026-10-22, the raw answers kept in `data-sources/cityctx/2026-10-22/` (`pnpm cityctx:fetch`, `pnpm cityctx:vox`, `pnpm cityctx`); laid flat in metres, simplified and put on a grid of half a metre (two metres for Seattle's city view) | OpenStreetMap's data is ODbL 1.0 (https://opendatacommons.org/licenses/odbl/1-0/), credited "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright"; the file is a derived database kept in its own file, apart from the CC0 files and from PLATEAU's data |
@@ -571,3 +573,11 @@ The dates, the venues and the figures of the 18 special games (`events`) were re
571
573
  | [2009 NHL Winter Classic Takes the Mound](https://www.sportsvideo.org/2008/12/30/2009-nhl-winter-classic-takes-the-mound/) | Carolyn Braff, Sports Video Group | 30 December 2008 | At Wrigley Field the 200- by 85-foot rink "splits the pitchers' mound from first to third" and "sits 112 feet from home plate and 288 feet from the center-field wall"; the 2008 and 2003 games were played on football fields. |
572
574
  | [Winter Classic at Wrigley evokes memories of 1st outdoor game at Cubs' home](https://www.nhl.com/news/topic/winter-classic/2009-wrigley-winter-classic-oral-history) | Tracey Myers, NHL.com | 29 December 2024 | Dan Craig, who built the 2009 rink: the diagram had center ice at second base, and the rink was moved inside the infield, so that center ice was basically at the pitching mound. |
573
575
  | [The NHL is about to move into Fenway Park. Here's a look at how it will set up for the Winter Classic.](https://www.bostonglobe.com/2022/12/18/sports/nhl-is-about-move-into-fenway-park-heres-look-how-it-will-set-up-winter-classic) | Michael Silverman, The Boston Globe | 18 December 2022 | At Fenway Park in 2023 the 200-foot rink is roughly centered length-wise across the first and third bases, stretched along a second-to-third-base axis across the Green Monster, shifted from its 2010 layout. |
576
+
577
+ ## The circuits' sources
578
+
579
+ The Las Vegas Strip Circuit, read on 2026-11-03 (`pnpm circuits:fetch`; the files are in `data-sources/circuits/2026-11-03/`: `osm-las-vegas.json` as Overpass returned it, with the query and the time of OpenStreetMap's data, `wikidata.json` and `wikipedia.json` with the infobox lines and the sentences read). The OpenStreetMap elements are ODbL 1.0, (c) OpenStreetMap contributors; the articles are CC BY-SA 4.0, read at the revisions below (the Japanese article's in [NOTICE.ja.md](./NOTICE.ja.md#サーキット)), and only facts are taken from them.
580
+
581
+ | Circuit | Wikidata item | OpenStreetMap | English article and revision |
582
+ | --- | --- | --- | --- |
583
+ | Las Vegas Strip Circuit | Q113112116 | relation/16696508 (the lap, the pit lane way/1223479152, the start node/13016217986, the finish node/13016217985, the 17 turns), the area way/233786564, the pit building way/1198427833 | Las Vegas Strip Circuit, 1376988769 |
package/README.md CHANGED
@@ -94,6 +94,27 @@ Real buildings drawn in the outline OpenStreetMap maps for them, each with a pla
94
94
  </tr>
95
95
  </table>
96
96
 
97
+ **Circuits** ([circuits](./docs/circuits.md))
98
+
99
+ <table align="center">
100
+ <tr>
101
+ <td align="center" valign="top">
102
+ <picture>
103
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/circuits-las-vegas-desk-dark.webp">
104
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/circuits-las-vegas-desk-light.webp" alt="The Las Vegas Strip Circuit from above: the lap as a dark ribbon with white edges, the pit building in its ground and a numbered badge at each of the 17 turns." width="250">
105
+ </picture>
106
+ <br><em>The Las Vegas Strip Circuit, its 17 turns numbered</em>
107
+ </td>
108
+ <td align="center" valign="top">
109
+ <picture>
110
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/circuits-las-vegas-3d-desk-dark.webp">
111
+ <img src="https://raw.githubusercontent.com/johnmorrisdotca/kyogijo-stadiums/main/docs/images/circuits-las-vegas-3d-desk-light.webp" alt="The same circuit in 3D from a low corner, with the pit building standing and each turn's number on a post." width="250">
112
+ </picture>
113
+ <br><em>The same lap in 3D</em>
114
+ </td>
115
+ </tr>
116
+ </table>
117
+
97
118
  **Districts and the fly-through** ([districts](./docs/district.md), [the fly-through](./docs/flythrough.md))
98
119
 
99
120
  <table align="center">
@@ -193,6 +214,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
193
214
  - **Every ballpark and ground**: The 30 current MLB parks and 44 that MLB clubs have left, and the 12 home parks of Nippon Professional Baseball with eight other NPB grounds, each with its fence drawn from the distances published for it; 64 soccer grounds at their published sizes; 167 arenas. [Ballparks](./docs/ballparks.md), [grounds](./docs/grounds.md), [arenas](./docs/arenas.md).
194
215
  - **Real venues**: arenas and stadiums in the outline of their buildings, districts of several venues on one plan, the crowd in the stands and outside. [Ballparks](./docs/ballparks.md), [venues](./docs/venues.md), [districts](./docs/district.md).
195
216
  - **A 3D view** of any surface or venue that turns by mouse, touch or keys, with the roof and the walls to switch and a quality level for every device. [The 3D view](./docs/turn.md), [quality](./docs/quality.md).
217
+ - **Circuits**: a famous race track as OpenStreetMap maps it, the Las Vegas Strip Circuit first, with its turns numbered and its pit building standing in 3D. [Circuits](./docs/circuits.md).
196
218
  - **Tournaments**: several surfaces on one floor, who judges where, boards laid over a surface. [Layouts](./docs/multi.md), [judging](./docs/judging.md), [boards](./docs/boards.md).
197
219
  - **Data on a surface**: points, heat maps, shot and pass charts, team shapes and a replay, in 48 providers' coordinate frames. [Layers](./docs/layers.md), [frames](./docs/frames.md), [replay](./docs/replay.md).
198
220
  - **Looks and flags**: eight named looks, a flag or crest on each half, trophies and crests from Dougu Sports, flags from Hata, light and dark. [Theming](./docs/theming.md), [the pageant](./docs/pageant.md).
@@ -207,6 +229,7 @@ console.log(svg.startsWith("<svg"), svg.includes("USA 3, Canada 2"));
207
229
  | `drawKyogijo`, `drawSurface`, `drawScore`, `drawBadge` | The drawing, as a string of SVG. |
208
230
  | `SPORTS`, `PARKS`, `COURT_THEMES`, `SOCCER_PITCHES`, `GROUNDS`, `EVENTS` | The forty-three sports, the 94 ballparks, the court looks, the pitches, the 64 grounds, the 18 special games. |
209
231
  | `./venues`, `./stadiums`, `./district`, `./turn` | Real arenas and stadiums in their buildings' outlines; districts on one plan; a surface as a 3D view that turns. |
232
+ | `./circuits` | Race tracks as OpenStreetMap maps them, in 2D and 3D ([demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/circuits.html)). |
210
233
  | `./multi`, `./judging`, `./layouts` | Several surfaces for one event ([demo](https://johnmorrisdotca.github.io/kyogijo-stadiums/halls.html)); who judges where; small multiples and backgrounds. |
211
234
  | `./layers`, `./charts`, `./frames`, `./zones`, `./pointer`, `./replay` | Data layers, charts, the providers' frames, zones by the rules, a click as a place, tracking data played on a surface. |
212
235
  | `./athletics`, `./budo`, `./skating` | The athletics stadium, the budō (areas, dohyō, the dojo builder) and the skating rinks. |
@@ -0,0 +1,11 @@
1
+ import "./strings.ts";
2
+ export { CIRCUITS, CIRCUIT_ATTRIBUTION, CIRCUIT_IDS, circuitById, circuitPlan } from "./circuits.ts";
3
+ export type { CircuitPlan, PlanTurn } from "./circuits.ts";
4
+ export { circuitDrawing, circuitTable, circuitText, drawCircuit } from "./circuits.draw.ts";
5
+ export type { CircuitDrawing } from "./circuits.draw.ts";
6
+ export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, mountCircuitTurn } from "./circuits.turn.ts";
7
+ export { KYOGIJO_CIRCUIT_STYLE } from "./circuits.style.ts";
8
+ export { CIRCUIT_WORDS } from "./circuits.words.ts";
9
+ export type { CircuitWords } from "./circuits.words.ts";
10
+ export { lineLength } from "./circuits.geo.ts";
11
+ export type { Circuit, CircuitBasis, CircuitData, CircuitFact, CircuitOptions, CircuitOsm, CircuitOsmPoint } from "./circuits.types.ts";
@@ -0,0 +1,9 @@
1
+ // The circuits entry (`@johnmorrisdotca/kyogijo-stadiums/circuits`): famous race tracks as OpenStreetMap maps them, with a few facts each, drawn flat and in 3D.
2
+ // The words of the 3D view's controls in both languages (the circuits' own are in circuits.words.ts).
3
+ import "./strings.js";
4
+ export { CIRCUITS, CIRCUIT_ATTRIBUTION, CIRCUIT_IDS, circuitById, circuitPlan } from "./circuits.js";
5
+ export { circuitDrawing, circuitTable, circuitText, drawCircuit } from "./circuits.draw.js";
6
+ export { CIRCUIT_MARKER, CIRCUIT_PLANES_MAX, CIRCUIT_PRESETS, buildCircuitTurn, circuitTurnMarkup, mountCircuitTurn } from "./circuits.turn.js";
7
+ export { KYOGIJO_CIRCUIT_STYLE } from "./circuits.style.js";
8
+ export { CIRCUIT_WORDS } from "./circuits.words.js";
9
+ export { lineLength } from "./circuits.geo.js";
@@ -0,0 +1,62 @@
1
+ import type { Pt } from "./crowd.ts";
2
+ import type { Circuit } from "./circuits.types.ts";
3
+ import { type LonLat } from "./venues.geo.ts";
4
+ /** The credit a drawing made from OpenStreetMap's data carries (the same words as the venues'). */
5
+ export declare const CIRCUIT_ATTRIBUTION = "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright";
6
+ /** Every circuit, in the order of the data. */
7
+ export declare const CIRCUITS: readonly Circuit[];
8
+ /** The keys of the circuits. */
9
+ export declare const CIRCUIT_IDS: readonly string[];
10
+ /** A circuit by its key, or `undefined`. */
11
+ export declare const circuitById: (id: string) => Circuit | undefined;
12
+ /** A turn as the plan places it: its number, its point on the lap, how far round the lap it is, which way the lap runs there and the side the bend turns to (1 left, -1 right, 0 straight). */
13
+ export interface PlanTurn {
14
+ n: number;
15
+ at: Pt;
16
+ along: number;
17
+ dir: Pt;
18
+ side: -1 | 0 | 1;
19
+ }
20
+ /** A circuit's plan in the drawing's frame: feet, the long side of the lap along x, y down the page. */
21
+ export interface CircuitPlan {
22
+ circuit: Circuit;
23
+ /** The point of the map the frame is measured from (the middle of the lap's box), `[longitude, latitude]`. */
24
+ origin: LonLat;
25
+ /** The heading of the lap's long side, degrees clockwise from north, and the way north points on the page, degrees clockwise from its top. */
26
+ heading: number;
27
+ north: number;
28
+ lap: Pt[];
29
+ /** The length of the lap measured along the drawn line, in metres. */
30
+ measured: number;
31
+ pitLane: Pt[];
32
+ area: Pt[];
33
+ building?: {
34
+ ring: Pt[];
35
+ height: number;
36
+ };
37
+ turns: PlanTurn[];
38
+ start: {
39
+ at: Pt;
40
+ dir: Pt;
41
+ along: number;
42
+ };
43
+ finish: {
44
+ at: Pt;
45
+ dir: Pt;
46
+ along: number;
47
+ };
48
+ /** The track's width and the pit lane's, in feet. */
49
+ width: number;
50
+ pitWidth: number;
51
+ /** The box of everything drawn on the ground, in feet. */
52
+ box: {
53
+ x0: number;
54
+ y0: number;
55
+ x1: number;
56
+ y1: number;
57
+ };
58
+ }
59
+ /** A circuit's plan: its lap, pit lane, ground, pit building, turns, start and finish in the drawing's frame. */
60
+ export declare function circuitPlan(circuit: Circuit): CircuitPlan;
61
+ /** The circuit a set of options names, or an error that lists the keys. */
62
+ export declare function circuitOf(id: string): Circuit;
@@ -0,0 +1,2 @@
1
+ import type { CircuitData } from "./circuits.types.ts";
2
+ export declare const CIRCUIT_DATA: CircuitData[];
@@ -0,0 +1,18 @@
1
+ export const CIRCUIT_DATA = [
2
+ {
3
+ "id": "las-vegas",
4
+ "wikidata": "Q113112116",
5
+ "en": { "title": "Las Vegas Strip Circuit", "revision": 1376988769 },
6
+ "ja": { "title": "ラスベガス・ストリップ・サーキット", "revision": 108197128 },
7
+ "names": { "en": "Las Vegas Strip Circuit", "ja": "ラスベガス・ストリップ・サーキット" },
8
+ "country": "US",
9
+ "opened": { "value": "2023-11-16", "basis": "documented", "source": "wikidata", "read": "P1619, date of official opening, 16 November 2023; the English article says it opened on November 16, 2023, and the Japanese infobox gives the same day" },
10
+ "length": { "value": 6201, "basis": "documented", "source": "wikipedia", "read": "the English infobox's length, 6.201 km (3.853 mi), which the Japanese infobox also gives; the Japanese article's prose says 6.120 km, which neither infobox nor the English article does, and is not used" },
11
+ "corners": { "value": 17, "basis": "documented", "source": "wikipedia", "read": "the English infobox's turns, 17, and its prose: 14 corners when announced, 17 after a chicane was added; the Japanese infobox gives 17 too" },
12
+ "direction": { "value": "counterclockwise", "basis": "documented", "source": "wikipedia", "read": "the English article: the street circuit runs counterclockwise; the Japanese article says the same (反時計回り)" },
13
+ "width": { "value": 12, "basis": "plausible", "read": "ours: no source we read gives the track's width (OpenStreetMap maps no width on the lap's ways, and neither article gives one), so the ribbon is drawn 12 metres wide" },
14
+ "pitLaneWidth": { "value": 8, "basis": "plausible", "read": "ours: no source gives the pit lane's width" },
15
+ "turnNumbers": { "basis": "documented", "source": "openstreetmap", "read": "the relation's member nodes tagged junction=yes with ref 1 to 17, one at each turn as OpenStreetMap maps them; the English article's account of the lap (Turn 1 a hairpin after the pits, Turns 10 and 11 on Sands Avenue, Turn 12 onto Las Vegas Boulevard, Turn 13 on the Strip, Turns 14 to 16 onto Harmon Avenue, Turn 17 before the pits) agrees with where they lie" },
16
+ "pitBuilding": { "basis": "documented", "source": "openstreetmap", "read": "the building OpenStreetMap maps inside the circuit's area beside the pit lane, 19.5 metres high in 4 levels (its height and building:levels tags)" },
17
+ },
18
+ ];
@@ -0,0 +1,56 @@
1
+ import type { Pt } from "./crowd.ts";
2
+ import { type CircuitPlan } from "./circuits.ts";
3
+ import type { CircuitOptions } from "./circuits.types.ts";
4
+ import type { Language } from "./types.ts";
5
+ /** A drawing of a circuit and what was worked out to make it. */
6
+ export interface CircuitDrawing {
7
+ svg: string;
8
+ plan: CircuitPlan;
9
+ /** The viewBox, in feet. */
10
+ viewBox: {
11
+ x: number;
12
+ y: number;
13
+ w: number;
14
+ h: number;
15
+ };
16
+ /** The size of the drawing's type and marks: a thousandth of its longer side, in feet. */
17
+ k: number;
18
+ /** Where each turn's number is drawn (its badge's middle), in feet, or would be when `turns` is `false`. */
19
+ badges: {
20
+ n: number;
21
+ at: Pt;
22
+ r: number;
23
+ }[];
24
+ title: string;
25
+ description: string;
26
+ }
27
+ /** The words of a circuit's drawing: its title and its description, sentence by sentence. */
28
+ export declare function circuitText(plan: CircuitPlan, language: Language, standing?: string): {
29
+ title: string;
30
+ description: string;
31
+ };
32
+ /** A circuit's drawing and what was worked out to make it. */
33
+ export declare function circuitDrawing(options: CircuitOptions): CircuitDrawing;
34
+ /**
35
+ * A circuit as one SVG string: the lap as a ribbon of the track's width on the ways OpenStreetMap maps for it, the pit lane and the circuit's ground with its pit building, the start and the chequered finish across the
36
+ * track, arrows the way the lap runs, each turn's number beside its bend where OpenStreetMap numbers it, a north arrow, a scale bar and the credit of the map's data. Light and dark follow the page.
37
+ * @example
38
+ * ```ts
39
+ * import { drawCircuit } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
40
+ * const svg = drawCircuit({ circuit: "las-vegas" });
41
+ * console.log(svg.startsWith("<svg"), svg.includes('data-turn="17"'), svg.includes("OpenStreetMap"));
42
+ * // true true true
43
+ * ```
44
+ */
45
+ export declare function drawCircuit(options: CircuitOptions): string;
46
+ /**
47
+ * A circuit's facts as a table, each with its basis (a source's, or ours) and the length the drawn line measures: for a reader who cannot see the drawing, and for a page that wants the data.
48
+ * @example
49
+ * ```ts
50
+ * import { circuitTable } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
51
+ * const table = circuitTable({ circuit: "las-vegas" });
52
+ * console.log(table.startsWith("<table"), table.includes("6.201 km"));
53
+ * // true true
54
+ * ```
55
+ */
56
+ export declare function circuitTable(options: CircuitOptions): string;
@@ -0,0 +1,265 @@
1
+ import { pointAlong } from "./circuits.geo.js";
2
+ import { circuitOf, circuitPlan, CIRCUIT_ATTRIBUTION } from "./circuits.js";
3
+ import { KYOGIJO_CIRCUIT_STYLE } from "./circuits.style.js";
4
+ import { CIRCUIT_WORDS } from "./circuits.words.js";
5
+ import { metaMarkup, safeClass } from "./common.js";
6
+ import { el, esc, hash, num } from "./format.js";
7
+ import { FEET_PER_METRE as FT } from "./geometry.js";
8
+ import { fill } from "./strings.core.js";
9
+ import { VERSION } from "./version.js";
10
+ const path = (points, closed) => `M${points.map(([x, y]) => `${num(x)} ${num(y)}`).join("L")}${closed ? "Z" : ""}`;
11
+ const right = ([dx, dy]) => [-dy, dx];
12
+ const add = (a, b, k = 1) => [a[0] + b[0] * k, a[1] + b[1] * k];
13
+ /** The metres of a length in the words' own way: one decimal of a kilometre. */
14
+ const km = (metres, language) => (metres / 1000).toLocaleString(language === "ja" ? "ja-JP" : "en-US", { minimumFractionDigits: 3, maximumFractionDigits: 3 });
15
+ /** The words of a circuit's drawing: its title and its description, sentence by sentence. */
16
+ export function circuitText(plan, language, standing) {
17
+ const c = plan.circuit;
18
+ const w = CIRCUIT_WORDS[language];
19
+ const name = c.names[language];
20
+ const gap = language === "ja" ? "" : " ";
21
+ const parts = [
22
+ fill(w.lap, { name, km: km(c.length.value, language), measured: km(plan.measured, language) }),
23
+ c.direction.value === "counterclockwise" ? w.counterclockwise : w.clockwise,
24
+ fill(w.turns, { turns: c.corners.value }),
25
+ w.pit,
26
+ ...(plan.building === undefined ? [] : [w.building]),
27
+ ...(c.width.basis === "plausible" ? [fill(w.width, { width: c.width.value })] : []),
28
+ ...(standing === undefined || standing === "" ? [] : [standing]),
29
+ fill(w.credit, { credit: CIRCUIT_ATTRIBUTION }),
30
+ ];
31
+ return { title: fill(w.title, { name }), description: parts.join(gap) };
32
+ }
33
+ /** The badges of the turns' numbers: each beside the outside of its bend, then pushed apart where two would touch, in the same few steps every time. */
34
+ function badgesOf(plan, r, k) {
35
+ const middle = [(plan.box.x0 + plan.box.x1) / 2, (plan.box.y0 + plan.box.y1) / 2];
36
+ const reach = plan.width / 2 + r + 4 * k;
37
+ const placed = plan.turns.map((t) => {
38
+ let normal = right(t.dir);
39
+ if (t.side === 1)
40
+ normal = right(t.dir);
41
+ else if (t.side === -1)
42
+ normal = [-normal[0], -normal[1]];
43
+ else if ((t.at[0] - middle[0]) * normal[0] + (t.at[1] - middle[1]) * normal[1] < 0)
44
+ normal = [-normal[0], -normal[1]];
45
+ return { n: t.n, at: add(t.at, normal, reach), r, from: t.at };
46
+ });
47
+ // Two badges that would touch are pushed apart along the line between them; a badge that would lie on the track is pushed off it.
48
+ for (let round = 0; round < 40; round += 1) {
49
+ let moved = false;
50
+ for (let i = 0; i < placed.length; i += 1) {
51
+ for (let j = i + 1; j < placed.length; j += 1) {
52
+ const [a, b] = [placed[i], placed[j]];
53
+ const dx = b.at[0] - a.at[0];
54
+ const dy = b.at[1] - a.at[1];
55
+ const d = Math.hypot(dx, dy);
56
+ const want = 2 * r + 2 * k;
57
+ if (d >= want)
58
+ continue;
59
+ const [ux, uy] = d < 1e-6 ? [1, 0] : [dx / d, dy / d];
60
+ const push = (want - d) / 2 + 0.01;
61
+ a.at = [a.at[0] - ux * push, a.at[1] - uy * push];
62
+ b.at = [b.at[0] + ux * push, b.at[1] + uy * push];
63
+ moved = true;
64
+ }
65
+ }
66
+ for (const b of placed) {
67
+ for (let i = 0; i < plan.lap.length; i += 1) {
68
+ const p = plan.lap[i];
69
+ const q = plan.lap[(i + 1) % plan.lap.length];
70
+ const ex = q[0] - p[0];
71
+ const ey = q[1] - p[1];
72
+ const len2 = ex * ex + ey * ey || 1;
73
+ const t = Math.min(1, Math.max(0, ((b.at[0] - p[0]) * ex + (b.at[1] - p[1]) * ey) / len2));
74
+ const near = [p[0] + t * ex, p[1] + t * ey];
75
+ const d = Math.hypot(b.at[0] - near[0], b.at[1] - near[1]);
76
+ const want = plan.width / 2 + r + k;
77
+ if (d >= want || d < 1e-6)
78
+ continue;
79
+ b.at = add(near, [(b.at[0] - near[0]) / d, (b.at[1] - near[1]) / d], want + 0.01);
80
+ moved = true;
81
+ }
82
+ }
83
+ if (!moved)
84
+ break;
85
+ }
86
+ return placed;
87
+ }
88
+ /** The finish line: two rows of squares across the track, black and white in turn, along the way the lap runs. */
89
+ function chequer(at, dir, width) {
90
+ const cells = 6;
91
+ const s = width / cells;
92
+ const across = right(dir);
93
+ let out = "";
94
+ for (let row = 0; row < 2; row += 1) {
95
+ for (let i = 0; i < cells; i += 1) {
96
+ const o = add(add(at, across, -width / 2 + i * s), dir, (row - 1) * s);
97
+ const corners = [o, add(o, across, s), add(add(o, across, s), dir, s), add(o, dir, s)];
98
+ out += el("path", { class: (i + row) % 2 === 0 ? "ky-ci-check-a" : "ky-ci-check-b", d: path(corners, true) });
99
+ }
100
+ }
101
+ return el("g", { class: "ky-ci-finish" }, out);
102
+ }
103
+ /** The north arrow at `at`, pointing `north` degrees clockwise from the top of the page, `size` long, with an N drawn as strokes (no font). */
104
+ function northArrow(at, north, size, label) {
105
+ const a = (north * Math.PI) / 180;
106
+ const u = [Math.sin(a), -Math.cos(a)];
107
+ const v = right(u);
108
+ const p = (along, side = 0) => {
109
+ const q = add(add(at, u, along * size), v, side * size);
110
+ return `${num(q[0])} ${num(q[1])}`;
111
+ };
112
+ const arrow = el("path", { class: "ky-ci-north", d: `M${p(-0.5)}L${p(0.5)}M${p(0.5)}L${p(0.18, 0.16)}L${p(0.18, -0.16)}Z`, "stroke-width": num(size * 0.06) });
113
+ // The N stands upright beside the arrow's tail, whichever way the arrow points.
114
+ const n = add(at, u, -0.5 * size - 0.32 * size);
115
+ const h = size * 0.16;
116
+ const letter = el("path", { class: "ky-ci-north-n", d: `M${num(n[0] - h * 0.75)} ${num(n[1] + h)}V${num(n[1] - h)}L${num(n[0] + h * 0.75)} ${num(n[1] + h)}V${num(n[1] - h)}`, "stroke-width": num(size * 0.055) });
117
+ return el("g", { class: "ky-ci-compass", "aria-label": label, role: "img" }, arrow + letter);
118
+ }
119
+ /** A scale bar of a round number of metres that is about a fifth of the drawing's width, at `at` (its left end), with its numbers. */
120
+ function scaleBar(at, width, k, unit) {
121
+ const steps = [100, 200, 250, 500, 1000, 2000];
122
+ const metres = steps.reduce((best, s) => (Math.abs(s * FT - width / 5) < Math.abs(best * FT - width / 5) ? s : best), steps[0]);
123
+ const long = metres * FT;
124
+ const h = 5 * k;
125
+ const text = 15 * k;
126
+ let out = el("rect", { class: "ky-ci-scale-fill", x: at[0], y: at[1] - h, width: long / 2, height: h });
127
+ out += el("rect", { class: "ky-ci-scale", x: at[0], y: at[1] - h, width: long, height: h, "stroke-width": num(1.4 * k) });
128
+ for (const [x, label] of [[0, "0"], [long / 2, String(metres / 2)], [long, `${metres} ${unit}`]]) {
129
+ out += el("text", { class: "ky-ci-scale-text", x: at[0] + x, y: at[1] - h - 5 * k, "font-size": num(text), "text-anchor": x === 0 ? "start" : x === long ? "end" : "middle" }, esc(label));
130
+ }
131
+ return el("g", { class: "ky-ci-scalebar", "data-metres": metres }, out);
132
+ }
133
+ /** The arrows along the lap: a few, evenly round it, each pointing the way the lap runs. */
134
+ function arrows(plan, k) {
135
+ const count = 8;
136
+ const total = plan.lap.reduce((sum, p, i) => sum + Math.hypot(plan.lap[(i + 1) % plan.lap.length][0] - p[0], plan.lap[(i + 1) % plan.lap.length][1] - p[1]), 0);
137
+ const size = Math.max(plan.width * 1.2, 15 * k);
138
+ let out = "";
139
+ for (let i = 0; i < count; i += 1) {
140
+ // Halfway between two of the evenly spaced places, so that none sits on the start.
141
+ const { at, dir } = pointAlong(plan.lap, plan.start.along + ((i + 0.5) * total) / count);
142
+ const side = right(dir);
143
+ const tip = add(at, dir, size * 0.6);
144
+ const back = add(at, dir, -size * 0.5);
145
+ out += el("path", { class: "ky-ci-arrow", d: path([tip, add(back, side, size * 0.45), add(at, dir, -size * 0.15), add(back, side, -size * 0.45)], true), "stroke-width": num(k * 0.8) });
146
+ }
147
+ return el("g", { class: "ky-ci-arrows" }, out);
148
+ }
149
+ /** A circuit's drawing and what was worked out to make it. */
150
+ export function circuitDrawing(options) {
151
+ const circuit = circuitOf(options.circuit);
152
+ const language = options.language === "ja" ? "ja" : "en";
153
+ const words = CIRCUIT_WORDS[language];
154
+ const plan = circuitPlan(circuit);
155
+ const label = options.label !== false;
156
+ const showTurns = options.turns !== false;
157
+ const base = Math.max(plan.box.x1 - plan.box.x0, plan.box.y1 - plan.box.y0);
158
+ const k = base / 1000;
159
+ const pad = 46 * k;
160
+ const head = label ? 70 * k : 0;
161
+ const foot = 44 * k;
162
+ const viewBox = { x: plan.box.x0 - pad, y: plan.box.y0 - pad - head, w: plan.box.x1 - plan.box.x0 + 2 * pad, h: plan.box.y1 - plan.box.y0 + 2 * pad + head + foot };
163
+ const gid = safeClass(options.id).replace(/ /g, "-") || `ky-ci-${hash(JSON.stringify([options, VERSION]))}`;
164
+ const { title, description } = circuitText(plan, language);
165
+ const r = 13 * k;
166
+ const placed = badgesOf(plan, r, k);
167
+ let body = el("rect", { class: "ky-ci-ground", x: viewBox.x, y: viewBox.y, width: viewBox.w, height: viewBox.h });
168
+ body += el("path", { class: "ky-ci-area", d: path(plan.area, true), "stroke-width": num(1.2 * k) });
169
+ if (plan.building !== undefined)
170
+ body += el("path", { class: "ky-ci-building", d: path(plan.building.ring, true), "stroke-width": num(1.2 * k), "data-height": num(plan.building.height / FT) });
171
+ const lapD = path(plan.lap, true);
172
+ const pitD = path(plan.pitLane, false);
173
+ const edge = Math.max(plan.width * 0.14, 1.1 * k);
174
+ body += el("path", { class: "ky-ci-halo", d: lapD, "stroke-width": num(plan.width + 9 * k) });
175
+ body += el("path", { class: "ky-ci-edge ky-ci-pit-edge", d: pitD, "stroke-width": num(plan.pitWidth + 2 * edge) });
176
+ body += el("path", { class: "ky-ci-pit", d: pitD, "stroke-width": num(plan.pitWidth), "data-part": "pit-lane" });
177
+ body += el("path", { class: "ky-ci-edge", d: lapD, "stroke-width": num(plan.width + 2 * edge) });
178
+ body += el("path", { class: "ky-ci-track", d: lapD, "stroke-width": num(plan.width), "data-part": "lap" });
179
+ if (options.arrows !== false)
180
+ body += arrows(plan, k);
181
+ // The start line: one white line across the track; the finish: a chequered band across it.
182
+ const across = right(plan.start.dir);
183
+ const s0 = add(plan.start.at, across, -plan.width / 2);
184
+ const s1 = add(plan.start.at, across, plan.width / 2);
185
+ body += el("path", { class: "ky-ci-start", d: path([s0, s1], false), "stroke-width": num(Math.max(1.6 * FT, 1.2 * k)), "data-part": "start" });
186
+ body += chequer(plan.finish.at, plan.finish.dir, plan.width);
187
+ // The turns' numbers, each with a thin line to its bend.
188
+ let turns = "";
189
+ for (const b of showTurns ? placed : []) {
190
+ const toward = Math.hypot(b.at[0] - b.from[0], b.at[1] - b.from[1]);
191
+ const lead = toward < 1e-6 ? b.from : add(b.from, [(b.at[0] - b.from[0]) / toward, (b.at[1] - b.from[1]) / toward], plan.width / 2);
192
+ let inner = el("path", { class: "ky-ci-leader", d: path([lead, b.at], false), "stroke-width": num(1.1 * k) });
193
+ inner += el("circle", { class: "ky-ci-badge", cx: b.at[0], cy: b.at[1], r: b.r, "stroke-width": num(1.4 * k) });
194
+ inner += el("text", { class: "ky-ci-badge-n", x: b.at[0], y: b.at[1] + b.r * 0.4, "font-size": num(b.r * (b.n >= 10 ? 1.05 : 1.25)) }, String(b.n));
195
+ turns += el("g", { class: "ky-ci-turn", "data-turn": b.n, "aria-label": fill(words.turn, { n: b.n }) }, inner);
196
+ }
197
+ body += el("g", { class: "ky-ci-turns" }, turns);
198
+ // The name and the facts in the top left; the north arrow in the top right; the scale bar and the credit along the foot.
199
+ if (label) {
200
+ body += el("text", { class: "ky-ci-name", x: viewBox.x + pad * 0.6, y: viewBox.y + 36 * k, "font-size": num(26 * k) }, esc(circuit.names[language]));
201
+ body += el("text", { class: "ky-ci-facts", x: viewBox.x + pad * 0.6, y: viewBox.y + 60 * k, "font-size": num(16 * k) }, esc(fill(words.facts, { km: km(circuit.length.value, language), turns: circuit.corners.value })));
202
+ }
203
+ body += northArrow([viewBox.x + viewBox.w - pad * 0.6 - 20 * k, viewBox.y + (label ? 44 * k : pad)], plan.north, 40 * k, words.northArrow);
204
+ body += scaleBar([viewBox.x + pad * 0.6, viewBox.y + viewBox.h - 14 * k], viewBox.w, k, words.metres);
205
+ body += el("text", { class: "ky-ci-credit", x: viewBox.x + viewBox.w - pad * 0.6, y: viewBox.y + viewBox.h - 14 * k, "font-size": num(11 * k), "text-anchor": "end" }, esc(CIRCUIT_ATTRIBUTION));
206
+ const titleId = `${gid}-t`;
207
+ const descId = `${gid}-d`;
208
+ const css = options.css === "none" ? "" : `<style>${KYOGIJO_CIRCUIT_STYLE}</style>`;
209
+ const svg = el("svg", {
210
+ xmlns: "http://www.w3.org/2000/svg",
211
+ class: `kyogijo ky-circuit${safeClass(options.className) === "" ? "" : ` ${safeClass(options.className)}`}`,
212
+ viewBox: `${num(viewBox.x)} ${num(viewBox.y)} ${num(viewBox.w)} ${num(viewBox.h)}`,
213
+ role: "img",
214
+ "aria-labelledby": `${titleId} ${descId}`,
215
+ "data-circuit": circuit.id,
216
+ "data-direction": circuit.direction.value,
217
+ "data-theme": options.theme === "light" || options.theme === "dark" ? options.theme : undefined,
218
+ "data-version": VERSION,
219
+ }, metaMarkup(titleId, title, descId, description) + css + body);
220
+ return { svg, plan, viewBox, k, badges: placed.map(({ n, at, r: radius }) => ({ n, at, r: radius })), title, description };
221
+ }
222
+ /**
223
+ * A circuit as one SVG string: the lap as a ribbon of the track's width on the ways OpenStreetMap maps for it, the pit lane and the circuit's ground with its pit building, the start and the chequered finish across the
224
+ * track, arrows the way the lap runs, each turn's number beside its bend where OpenStreetMap numbers it, a north arrow, a scale bar and the credit of the map's data. Light and dark follow the page.
225
+ * @example
226
+ * ```ts
227
+ * import { drawCircuit } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
228
+ * const svg = drawCircuit({ circuit: "las-vegas" });
229
+ * console.log(svg.startsWith("<svg"), svg.includes('data-turn="17"'), svg.includes("OpenStreetMap"));
230
+ * // true true true
231
+ * ```
232
+ */
233
+ export function drawCircuit(options) {
234
+ return circuitDrawing(options).svg;
235
+ }
236
+ /**
237
+ * A circuit's facts as a table, each with its basis (a source's, or ours) and the length the drawn line measures: for a reader who cannot see the drawing, and for a page that wants the data.
238
+ * @example
239
+ * ```ts
240
+ * import { circuitTable } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
241
+ * const table = circuitTable({ circuit: "las-vegas" });
242
+ * console.log(table.startsWith("<table"), table.includes("6.201 km"));
243
+ * // true true
244
+ * ```
245
+ */
246
+ export function circuitTable(options) {
247
+ const language = options.language === "ja" ? "ja" : "en";
248
+ const w = CIRCUIT_WORDS[language].table;
249
+ const plan = circuitPlan(circuitOf(options.circuit));
250
+ const c = plan.circuit;
251
+ const basis = (b) => (b === "documented" ? w.documented : w.plausible);
252
+ const rows = [
253
+ [w.length, `${km(c.length.value, language)} km`, basis(c.length.basis)],
254
+ [w.measured, `${km(plan.measured, language)} km`, "OpenStreetMap"],
255
+ [w.corners, String(c.corners.value), basis(c.corners.basis)],
256
+ [w.direction, c.direction.value === "counterclockwise" ? w.counterclockwise : w.clockwise, basis(c.direction.basis)],
257
+ [w.opened, c.opened.value, basis(c.opened.basis)],
258
+ [w.width, `${c.width.value} m`, basis(c.width.basis)],
259
+ [w.pitLaneWidth, `${c.pitLaneWidth.value} m`, basis(c.pitLaneWidth.basis)],
260
+ [w.wikidata, c.wikidata, "Wikidata"],
261
+ ];
262
+ const head = `<thead><tr><th scope="col">${esc(w.fact)}</th><th scope="col">${esc(w.value)}</th><th scope="col">${esc(w.basis)}</th></tr></thead>`;
263
+ const body = rows.map(([a, b, d]) => `<tr><th scope="row">${esc(a)}</th><td>${esc(b)}</td><td>${esc(d)}</td></tr>`).join("");
264
+ return `<table class="ky-table" data-kind="circuit"><caption>${esc(fill(w.caption, { name: c.names[language] }))}</caption>${head}<tbody>${body}</tbody></table>`;
265
+ }
@@ -0,0 +1,29 @@
1
+ import type { Pt } from "./crowd.ts";
2
+ /** The length of a line, closed into a ring when `closed`. */
3
+ export declare function lineLength(points: readonly Pt[], closed: boolean): number;
4
+ /** An open line with the points that lie within `tolerance` of the line between their neighbours taken out (Douglas and Peucker); the two ends are kept. */
5
+ export declare function simplifyLine(points: readonly Pt[], tolerance: number): Pt[];
6
+ /** A ring (the first point not repeated) made simpler as a line from its first point round to it again, so that the first point, the start of a lap, stays where it is. */
7
+ export declare function simplifyLoop(points: readonly Pt[], tolerance: number): Pt[];
8
+ /** Where on a line (closed when `closed`) the point nearest `p` is: the segment it is on (from point `i` to the next), how far round from the first point that is, the point itself and the distance to it. */
9
+ export declare function nearestOn(points: readonly Pt[], p: Pt, closed: boolean): {
10
+ i: number;
11
+ along: number;
12
+ at: Pt;
13
+ d: number;
14
+ };
15
+ /** The point and the unit direction of a ring `along` units round from its first point (wrapped round the lap). */
16
+ export declare function pointAlong(points: readonly Pt[], along: number): {
17
+ at: Pt;
18
+ dir: Pt;
19
+ };
20
+ /**
21
+ * Which way a ring bends at `along`, worked out over `reach` either side: the unit direction of travel there and the side the bend turns to (`1` to the left of travel in a frame whose y grows down the page, as an SVG's does,
22
+ * `-1` to the right, `0` straight).
23
+ */
24
+ export declare function bendAt(points: readonly Pt[], along: number, reach: number): {
25
+ dir: Pt;
26
+ side: -1 | 0 | 1;
27
+ };
28
+ /** The signed area of a ring (the shoelace sum, halved): positive when it runs clockwise on a page whose y grows down. */
29
+ export declare function signedAreaOf(points: readonly Pt[]): number;