@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 +18 -0
- package/NOTICE.md +10 -0
- package/README.md +23 -0
- package/dist/circuits-entry.d.ts +11 -0
- package/dist/circuits-entry.js +9 -0
- package/dist/circuits.d.ts +62 -0
- package/dist/circuits.data.d.ts +2 -0
- package/dist/circuits.data.js +18 -0
- package/dist/circuits.draw.d.ts +56 -0
- package/dist/circuits.draw.js +265 -0
- package/dist/circuits.geo.d.ts +29 -0
- package/dist/circuits.geo.js +105 -0
- package/dist/circuits.js +73 -0
- package/dist/circuits.osm.data.d.ts +2 -0
- package/dist/circuits.osm.data.js +188 -0
- package/dist/circuits.style.d.ts +5 -0
- package/dist/circuits.style.js +34 -0
- package/dist/circuits.turn.d.ts +43 -0
- package/dist/circuits.turn.js +119 -0
- package/dist/circuits.types.d.ts +121 -0
- package/dist/circuits.types.js +1 -0
- package/dist/circuits.words.d.ts +47 -0
- package/dist/circuits.words.js +38 -0
- package/dist/multi.js +17 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** The length of a line, closed into a ring when `closed`. */
|
|
2
|
+
export function lineLength(points, closed) {
|
|
3
|
+
let sum = 0;
|
|
4
|
+
const last = closed ? points.length : points.length - 1;
|
|
5
|
+
for (let i = 0; i < last; i += 1) {
|
|
6
|
+
const p = points[i];
|
|
7
|
+
const q = points[(i + 1) % points.length];
|
|
8
|
+
sum += Math.hypot(q[0] - p[0], q[1] - p[1]);
|
|
9
|
+
}
|
|
10
|
+
return sum;
|
|
11
|
+
}
|
|
12
|
+
/** The distance from `p` to the segment from `a` to `b`, and how far along it (0 to 1) the nearest point is. */
|
|
13
|
+
function toSegment(p, a, b) {
|
|
14
|
+
const dx = b[0] - a[0];
|
|
15
|
+
const dy = b[1] - a[1];
|
|
16
|
+
const len2 = dx * dx + dy * dy;
|
|
17
|
+
const t = len2 === 0 ? 0 : Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
|
|
18
|
+
return { d: Math.hypot(a[0] + t * dx - p[0], a[1] + t * dy - p[1]), t };
|
|
19
|
+
}
|
|
20
|
+
/** 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. */
|
|
21
|
+
export function simplifyLine(points, tolerance) {
|
|
22
|
+
if (points.length < 3)
|
|
23
|
+
return [...points];
|
|
24
|
+
const keep = new Array(points.length).fill(false);
|
|
25
|
+
keep[0] = true;
|
|
26
|
+
keep[points.length - 1] = true;
|
|
27
|
+
const stack = [[0, points.length - 1]];
|
|
28
|
+
while (stack.length > 0) {
|
|
29
|
+
const [from, to] = stack.pop();
|
|
30
|
+
let at = -1;
|
|
31
|
+
let most = tolerance;
|
|
32
|
+
for (let i = from + 1; i < to; i += 1) {
|
|
33
|
+
const { d } = toSegment(points[i], points[from], points[to]);
|
|
34
|
+
if (d > most)
|
|
35
|
+
[at, most] = [i, d];
|
|
36
|
+
}
|
|
37
|
+
if (at >= 0) {
|
|
38
|
+
keep[at] = true;
|
|
39
|
+
stack.push([from, at], [at, to]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return points.filter((_, i) => keep[i]);
|
|
43
|
+
}
|
|
44
|
+
/** 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. */
|
|
45
|
+
export function simplifyLoop(points, tolerance) {
|
|
46
|
+
const line = simplifyLine([...points, points[0]], tolerance);
|
|
47
|
+
return line.slice(0, -1);
|
|
48
|
+
}
|
|
49
|
+
/** 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. */
|
|
50
|
+
export function nearestOn(points, p, closed) {
|
|
51
|
+
let best = { i: 0, along: 0, at: points[0], d: Infinity };
|
|
52
|
+
let walked = 0;
|
|
53
|
+
const last = closed ? points.length : points.length - 1;
|
|
54
|
+
for (let i = 0; i < last; i += 1) {
|
|
55
|
+
const a = points[i];
|
|
56
|
+
const b = points[(i + 1) % points.length];
|
|
57
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
58
|
+
const { d, t } = toSegment(p, a, b);
|
|
59
|
+
if (d < best.d)
|
|
60
|
+
best = { i, along: walked + t * long, at: [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])], d };
|
|
61
|
+
walked += long;
|
|
62
|
+
}
|
|
63
|
+
return best;
|
|
64
|
+
}
|
|
65
|
+
/** The point and the unit direction of a ring `along` units round from its first point (wrapped round the lap). */
|
|
66
|
+
export function pointAlong(points, along) {
|
|
67
|
+
const total = lineLength(points, true);
|
|
68
|
+
let left = ((along % total) + total) % total;
|
|
69
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
70
|
+
const a = points[i];
|
|
71
|
+
const b = points[(i + 1) % points.length];
|
|
72
|
+
const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
73
|
+
if (left <= long || i === points.length - 1) {
|
|
74
|
+
const t = long === 0 ? 0 : Math.min(1, left / long);
|
|
75
|
+
return { at: [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])], dir: long === 0 ? [1, 0] : [(b[0] - a[0]) / long, (b[1] - a[1]) / long] };
|
|
76
|
+
}
|
|
77
|
+
left -= long;
|
|
78
|
+
}
|
|
79
|
+
return { at: points[0], dir: [1, 0] };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 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,
|
|
83
|
+
* `-1` to the right, `0` straight).
|
|
84
|
+
*/
|
|
85
|
+
export function bendAt(points, along, reach) {
|
|
86
|
+
const before = pointAlong(points, along - reach).at;
|
|
87
|
+
const here = pointAlong(points, along);
|
|
88
|
+
const after = pointAlong(points, along + reach).at;
|
|
89
|
+
const a = [here.at[0] - before[0], here.at[1] - before[1]];
|
|
90
|
+
const b = [after[0] - here.at[0], after[1] - here.at[1]];
|
|
91
|
+
const cross = a[0] * b[1] - a[1] * b[0];
|
|
92
|
+
const scale = Math.hypot(...a) * Math.hypot(...b);
|
|
93
|
+
const side = scale === 0 || Math.abs(cross / scale) < 0.02 ? 0 : cross < 0 ? 1 : -1;
|
|
94
|
+
return { dir: here.dir, side };
|
|
95
|
+
}
|
|
96
|
+
/** The signed area of a ring (the shoelace sum, halved): positive when it runs clockwise on a page whose y grows down. */
|
|
97
|
+
export function signedAreaOf(points) {
|
|
98
|
+
let sum = 0;
|
|
99
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
100
|
+
const p = points[i];
|
|
101
|
+
const q = points[(i + 1) % points.length];
|
|
102
|
+
sum += p[0] * q[1] - q[0] * p[1];
|
|
103
|
+
}
|
|
104
|
+
return sum / 2;
|
|
105
|
+
}
|
package/dist/circuits.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CIRCUIT_DATA } from "./circuits.data.js";
|
|
2
|
+
import { CIRCUIT_OSM } from "./circuits.osm.data.js";
|
|
3
|
+
import { bendAt, lineLength, nearestOn } from "./circuits.geo.js";
|
|
4
|
+
import { FEET_PER_METRE as FT } from "./geometry.js";
|
|
5
|
+
import { metresFrom, northOn, smallestBox, turnedTo } from "./venues.geo.js";
|
|
6
|
+
/** The credit a drawing made from OpenStreetMap's data carries (the same words as the venues'). */
|
|
7
|
+
export const CIRCUIT_ATTRIBUTION = "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright";
|
|
8
|
+
/** Every circuit, in the order of the data. */
|
|
9
|
+
export const CIRCUITS = Object.freeze(CIRCUIT_DATA.map((c) => {
|
|
10
|
+
const osm = CIRCUIT_OSM[c.id];
|
|
11
|
+
if (osm === undefined)
|
|
12
|
+
throw new Error(`the circuit ${c.id} has no OpenStreetMap record`);
|
|
13
|
+
return Object.freeze({ ...c, osm });
|
|
14
|
+
}));
|
|
15
|
+
/** The keys of the circuits. */
|
|
16
|
+
export const CIRCUIT_IDS = Object.freeze(CIRCUITS.map((c) => c.id));
|
|
17
|
+
/** A circuit by its key, or `undefined`. */
|
|
18
|
+
export const circuitById = (id) => CIRCUITS.find((c) => c.id === id);
|
|
19
|
+
const points = (osm) => [...osm.lap, ...osm.pitLane.line, ...osm.area.ring, ...(osm.pitBuilding?.ring ?? [])];
|
|
20
|
+
/** A circuit's plan: its lap, pit lane, ground, pit building, turns, start and finish in the drawing's frame. */
|
|
21
|
+
export function circuitPlan(circuit) {
|
|
22
|
+
const osm = circuit.osm;
|
|
23
|
+
const all = points(osm);
|
|
24
|
+
const lons = all.map((p) => p[0]);
|
|
25
|
+
const lats = all.map((p) => p[1]);
|
|
26
|
+
const origin = [(Math.min(...lons) + Math.max(...lons)) / 2, (Math.min(...lats) + Math.max(...lats)) / 2];
|
|
27
|
+
const lapMetres = metresFrom(origin, osm.lap);
|
|
28
|
+
const heading = smallestBox(lapMetres).heading;
|
|
29
|
+
const frame = (ring) => metresFrom(origin, ring).map((p) => {
|
|
30
|
+
const [x, y] = turnedTo(heading, p);
|
|
31
|
+
return [x * FT, y * FT];
|
|
32
|
+
});
|
|
33
|
+
const lap = frame(osm.lap);
|
|
34
|
+
const place = (at) => {
|
|
35
|
+
const near = nearestOn(lap, frame([at])[0], true);
|
|
36
|
+
const bend = bendAt(lap, near.along, 30 * FT);
|
|
37
|
+
return { at: near.at, dir: bend.dir, along: near.along };
|
|
38
|
+
};
|
|
39
|
+
const turns = osm.turns.map((t) => {
|
|
40
|
+
const near = nearestOn(lap, frame([t.at])[0], true);
|
|
41
|
+
const bend = bendAt(lap, near.along, 40 * FT);
|
|
42
|
+
return { n: t.n, at: near.at, along: near.along, dir: bend.dir, side: bend.side };
|
|
43
|
+
});
|
|
44
|
+
const pitLane = frame(osm.pitLane.line);
|
|
45
|
+
const area = frame(osm.area.ring);
|
|
46
|
+
const building = osm.pitBuilding === undefined ? undefined : { ring: frame(osm.pitBuilding.ring), height: osm.pitBuilding.height * FT };
|
|
47
|
+
const xs = [...lap, ...pitLane, ...area, ...(building?.ring ?? [])].map((p) => p[0]);
|
|
48
|
+
const ys = [...lap, ...pitLane, ...area, ...(building?.ring ?? [])].map((p) => p[1]);
|
|
49
|
+
return {
|
|
50
|
+
circuit,
|
|
51
|
+
origin,
|
|
52
|
+
heading,
|
|
53
|
+
north: northOn(heading),
|
|
54
|
+
lap,
|
|
55
|
+
measured: lineLength(lapMetres, true),
|
|
56
|
+
pitLane,
|
|
57
|
+
area,
|
|
58
|
+
...(building === undefined ? {} : { building }),
|
|
59
|
+
turns,
|
|
60
|
+
start: place(osm.start.at),
|
|
61
|
+
finish: place(osm.finish.at),
|
|
62
|
+
width: circuit.width.value * FT,
|
|
63
|
+
pitWidth: circuit.pitLaneWidth.value * FT,
|
|
64
|
+
box: { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** The circuit a set of options names, or an error that lists the keys. */
|
|
68
|
+
export function circuitOf(id) {
|
|
69
|
+
const found = circuitById(id);
|
|
70
|
+
if (found === undefined)
|
|
71
|
+
throw new Error(`no circuit "${String(id)}": the circuits are ${CIRCUIT_IDS.join(", ")}`);
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
export const CIRCUIT_OSM = {
|
|
2
|
+
"las-vegas": {
|
|
3
|
+
"source": "relation/16696508",
|
|
4
|
+
"read": "2026-11-03",
|
|
5
|
+
"base": "2026-11-03T08:11:25Z",
|
|
6
|
+
"licence": "ODbL-1.0",
|
|
7
|
+
"attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
|
|
8
|
+
"points": 228,
|
|
9
|
+
"tolerance": 0.5,
|
|
10
|
+
"start": { "source": "node/13016217986", "at": [-115.161882, 36.109284] },
|
|
11
|
+
"finish": { "source": "node/13016217985", "at": [-115.16256, 36.108665] },
|
|
12
|
+
"turns": [
|
|
13
|
+
{ "n": 1, "source": "node/11348753612", "at": [-115.160364, 36.1107] },
|
|
14
|
+
{ "n": 2, "source": "node/11348753615", "at": [-115.160527, 36.111068] },
|
|
15
|
+
{ "n": 3, "source": "node/11348753622", "at": [-115.162922, 36.110443] },
|
|
16
|
+
{ "n": 4, "source": "node/5717585567", "at": [-115.163928, 36.111154] },
|
|
17
|
+
{ "n": 5, "source": "node/137743393", "at": [-115.163995, 36.119208] },
|
|
18
|
+
{ "n": 6, "source": "node/12626696512", "at": [-115.161185, 36.119173] },
|
|
19
|
+
{ "n": 7, "source": "node/12638889512", "at": [-115.160113, 36.120741] },
|
|
20
|
+
{ "n": 8, "source": "node/12638889528", "at": [-115.160671, 36.120939] },
|
|
21
|
+
{ "n": 9, "source": "node/12059212382", "at": [-115.160406, 36.122236] },
|
|
22
|
+
{ "n": 10, "source": "node/137402143", "at": [-115.165999, 36.123073] },
|
|
23
|
+
{ "n": 11, "source": "node/13250780586", "at": [-115.166888, 36.124717] },
|
|
24
|
+
{ "n": 12, "source": "node/1490596713", "at": [-115.169378, 36.125559] },
|
|
25
|
+
{ "n": 13, "source": "node/1598856720", "at": [-115.172521, 36.120102] },
|
|
26
|
+
{ "n": 14, "source": "node/2852160857", "at": [-115.173152, 36.108811] },
|
|
27
|
+
{ "n": 15, "source": "node/2852160859", "at": [-115.172805, 36.108527] },
|
|
28
|
+
{ "n": 16, "source": "node/2852160864", "at": [-115.171797, 36.10798] },
|
|
29
|
+
{ "n": 17, "source": "node/13341696778", "at": [-115.163901, 36.10797] },
|
|
30
|
+
],
|
|
31
|
+
"pitLane": { "source": "way/1223479152", "line": [
|
|
32
|
+
[-115.163901, 36.10797],
|
|
33
|
+
[-115.163719, 36.108128],
|
|
34
|
+
[-115.163534, 36.108211],
|
|
35
|
+
[-115.163399, 36.108284],
|
|
36
|
+
[-115.163327, 36.108348],
|
|
37
|
+
[-115.163159, 36.108544],
|
|
38
|
+
[-115.163088, 36.108588],
|
|
39
|
+
[-115.163006, 36.108606],
|
|
40
|
+
[-115.162864, 36.108617],
|
|
41
|
+
[-115.162772, 36.108643],
|
|
42
|
+
[-115.160585, 36.110632],
|
|
43
|
+
[-115.160527, 36.110733],
|
|
44
|
+
[-115.160525, 36.110799],
|
|
45
|
+
[-115.160546, 36.110885],
|
|
46
|
+
[-115.160615, 36.110975],
|
|
47
|
+
[-115.160786, 36.111152],
|
|
48
|
+
] },
|
|
49
|
+
"area": { "source": "way/233786564", "ring": [
|
|
50
|
+
[-115.159474, 36.108123],
|
|
51
|
+
[-115.159465, 36.109009],
|
|
52
|
+
[-115.159463, 36.111419],
|
|
53
|
+
[-115.163831, 36.111463],
|
|
54
|
+
[-115.16387, 36.111421],
|
|
55
|
+
[-115.163831, 36.108924],
|
|
56
|
+
[-115.16384, 36.108175],
|
|
57
|
+
[-115.163275, 36.108185],
|
|
58
|
+
[-115.163113, 36.108111],
|
|
59
|
+
] },
|
|
60
|
+
"pitBuilding": { "source": "way/1198427833", "height": 19.5, "levels": 4, "ring": [
|
|
61
|
+
[-115.160882, 36.110929],
|
|
62
|
+
[-115.160837, 36.110969],
|
|
63
|
+
[-115.160746, 36.110905],
|
|
64
|
+
[-115.160791, 36.110864],
|
|
65
|
+
[-115.160627, 36.110748],
|
|
66
|
+
[-115.162844, 36.108712],
|
|
67
|
+
[-115.16312, 36.108908],
|
|
68
|
+
[-115.162957, 36.109058],
|
|
69
|
+
[-115.163191, 36.108998],
|
|
70
|
+
[-115.163351, 36.109117],
|
|
71
|
+
[-115.162718, 36.109277],
|
|
72
|
+
[-115.16103, 36.110828],
|
|
73
|
+
[-115.161186, 36.11098],
|
|
74
|
+
[-115.161052, 36.111078],
|
|
75
|
+
[-115.160911, 36.111007],
|
|
76
|
+
[-115.160944, 36.110973],
|
|
77
|
+
] },
|
|
78
|
+
"lap": [
|
|
79
|
+
[-115.161882, 36.109284],
|
|
80
|
+
[-115.160457, 36.110584],
|
|
81
|
+
[-115.160364, 36.1107],
|
|
82
|
+
[-115.160339, 36.110795],
|
|
83
|
+
[-115.160346, 36.110874],
|
|
84
|
+
[-115.160416, 36.110984],
|
|
85
|
+
[-115.160527, 36.111068],
|
|
86
|
+
[-115.160644, 36.111116],
|
|
87
|
+
[-115.160786, 36.111152],
|
|
88
|
+
[-115.160999, 36.111176],
|
|
89
|
+
[-115.161206, 36.111167],
|
|
90
|
+
[-115.161388, 36.111125],
|
|
91
|
+
[-115.161573, 36.111048],
|
|
92
|
+
[-115.16184, 36.110903],
|
|
93
|
+
[-115.162253, 36.110584],
|
|
94
|
+
[-115.162388, 36.110509],
|
|
95
|
+
[-115.162551, 36.110465],
|
|
96
|
+
[-115.162753, 36.110441],
|
|
97
|
+
[-115.162922, 36.110443],
|
|
98
|
+
[-115.163104, 36.110489],
|
|
99
|
+
[-115.163265, 36.11054],
|
|
100
|
+
[-115.163431, 36.110615],
|
|
101
|
+
[-115.163611, 36.11074],
|
|
102
|
+
[-115.163758, 36.110892],
|
|
103
|
+
[-115.163928, 36.111154],
|
|
104
|
+
[-115.163941, 36.111772],
|
|
105
|
+
[-115.163941, 36.11265],
|
|
106
|
+
[-115.163969, 36.114338],
|
|
107
|
+
[-115.163963, 36.115729],
|
|
108
|
+
[-115.163995, 36.119208],
|
|
109
|
+
[-115.162977, 36.119205],
|
|
110
|
+
[-115.161185, 36.119173],
|
|
111
|
+
[-115.160922, 36.119319],
|
|
112
|
+
[-115.160561, 36.119603],
|
|
113
|
+
[-115.1604, 36.119784],
|
|
114
|
+
[-115.160199, 36.120147],
|
|
115
|
+
[-115.160142, 36.120366],
|
|
116
|
+
[-115.160112, 36.120615],
|
|
117
|
+
[-115.160113, 36.120741],
|
|
118
|
+
[-115.160393, 36.120792],
|
|
119
|
+
[-115.160477, 36.120812],
|
|
120
|
+
[-115.160518, 36.12083],
|
|
121
|
+
[-115.160612, 36.120889],
|
|
122
|
+
[-115.160671, 36.120939],
|
|
123
|
+
[-115.160701, 36.120982],
|
|
124
|
+
[-115.160739, 36.121078],
|
|
125
|
+
[-115.160746, 36.121199],
|
|
126
|
+
[-115.160715, 36.121326],
|
|
127
|
+
[-115.160619, 36.121508],
|
|
128
|
+
[-115.160428, 36.121821],
|
|
129
|
+
[-115.160418, 36.121862],
|
|
130
|
+
[-115.160406, 36.122236],
|
|
131
|
+
[-115.161621, 36.122255],
|
|
132
|
+
[-115.163038, 36.122292],
|
|
133
|
+
[-115.16404, 36.12229],
|
|
134
|
+
[-115.164161, 36.122469],
|
|
135
|
+
[-115.164789, 36.122495],
|
|
136
|
+
[-115.165233, 36.122578],
|
|
137
|
+
[-115.165486, 36.122681],
|
|
138
|
+
[-115.165771, 36.122882],
|
|
139
|
+
[-115.165999, 36.123073],
|
|
140
|
+
[-115.166099, 36.123197],
|
|
141
|
+
[-115.166263, 36.123475],
|
|
142
|
+
[-115.166719, 36.124462],
|
|
143
|
+
[-115.1668, 36.124594],
|
|
144
|
+
[-115.166888, 36.124717],
|
|
145
|
+
[-115.166993, 36.124807],
|
|
146
|
+
[-115.167403, 36.125109],
|
|
147
|
+
[-115.167607, 36.125224],
|
|
148
|
+
[-115.167978, 36.125335],
|
|
149
|
+
[-115.168348, 36.125409],
|
|
150
|
+
[-115.169152, 36.125532],
|
|
151
|
+
[-115.169378, 36.125559],
|
|
152
|
+
[-115.170175, 36.124296],
|
|
153
|
+
[-115.170827, 36.123304],
|
|
154
|
+
[-115.1716, 36.122216],
|
|
155
|
+
[-115.172014, 36.121456],
|
|
156
|
+
[-115.17229, 36.120739],
|
|
157
|
+
[-115.172521, 36.120102],
|
|
158
|
+
[-115.172634, 36.119746],
|
|
159
|
+
[-115.172763, 36.119258],
|
|
160
|
+
[-115.172829, 36.118921],
|
|
161
|
+
[-115.172938, 36.118154],
|
|
162
|
+
[-115.172984, 36.117475],
|
|
163
|
+
[-115.172992, 36.115944],
|
|
164
|
+
[-115.172983, 36.11477],
|
|
165
|
+
[-115.17303, 36.114029],
|
|
166
|
+
[-115.173048, 36.113017],
|
|
167
|
+
[-115.173134, 36.110958],
|
|
168
|
+
[-115.173145, 36.109883],
|
|
169
|
+
[-115.173142, 36.109147],
|
|
170
|
+
[-115.173152, 36.108811],
|
|
171
|
+
[-115.172561, 36.108329],
|
|
172
|
+
[-115.172342, 36.108166],
|
|
173
|
+
[-115.172192, 36.108065],
|
|
174
|
+
[-115.172108, 36.108025],
|
|
175
|
+
[-115.172026, 36.108002],
|
|
176
|
+
[-115.171915, 36.107986],
|
|
177
|
+
[-115.171283, 36.107967],
|
|
178
|
+
[-115.170195, 36.107965],
|
|
179
|
+
[-115.168707, 36.10795],
|
|
180
|
+
[-115.164027, 36.107952],
|
|
181
|
+
[-115.163901, 36.10797],
|
|
182
|
+
[-115.163509, 36.10809],
|
|
183
|
+
[-115.163145, 36.108223],
|
|
184
|
+
[-115.162981, 36.108306],
|
|
185
|
+
[-115.162837, 36.108412],
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The colours in light and in dark. */
|
|
2
|
+
export declare const CIRCUIT_LIGHT = "--ky-ci-ground:#eceae3;--ky-ci-area:#dcd7cb;--ky-ci-area-edge:#bdb6a6;--ky-ci-building:#c2bbad;--ky-ci-building-edge:#7f786b;--ky-ci-track:#3a3f46;--ky-ci-edge:#ffffff;--ky-ci-halo:rgba(58,63,70,.16);--ky-ci-pit:#5d636b;--ky-ci-line:#ffffff;--ky-ci-check:#15181c;--ky-ci-arrow:#f2c230;--ky-ci-arrow-edge:#3a3f46;--ky-ci-badge:#14213d;--ky-ci-badge-ink:#ffffff;--ky-ci-leader:#14213d;--ky-ci-ink:#1f2320;--ky-ci-muted:#4b535c";
|
|
3
|
+
export declare const CIRCUIT_DARK = "--ky-ci-ground:#10151b;--ky-ci-area:#1b232c;--ky-ci-area-edge:#334150;--ky-ci-building:#3a4756;--ky-ci-building-edge:#6f7f90;--ky-ci-track:#5d646e;--ky-ci-edge:#e6e9ed;--ky-ci-halo:rgba(230,233,237,.1);--ky-ci-pit:#7a838e;--ky-ci-line:#ffffff;--ky-ci-check:#0b0d10;--ky-ci-arrow:#f2c230;--ky-ci-arrow-edge:#10151b;--ky-ci-badge:#e9edf2;--ky-ci-badge-ink:#10151b;--ky-ci-leader:#c4ccd5;--ky-ci-ink:#eef1f4;--ky-ci-muted:#aab3bd";
|
|
4
|
+
/** The rules of a circuit's drawing, and of the turn markers and the pit building of its 3D view (`.ky-turn`). */
|
|
5
|
+
export declare const KYOGIJO_CIRCUIT_STYLE: string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The rules of a circuit's drawing and of what stands up in its 3D view, light and dark. The colours are custom properties (`--ky-ci-*`) a page can set; `:where` keeps their weight at nothing so that a page's own win. The
|
|
2
|
+
// drawing follows the device and the page, and a `data-theme` of light or dark on it or on the page forces one. Nothing here is a team's, a race's or a sponsor's colour: asphalt, paint and paper.
|
|
3
|
+
/** The colours in light and in dark. */
|
|
4
|
+
export const CIRCUIT_LIGHT = "--ky-ci-ground:#eceae3;--ky-ci-area:#dcd7cb;--ky-ci-area-edge:#bdb6a6;--ky-ci-building:#c2bbad;--ky-ci-building-edge:#7f786b;--ky-ci-track:#3a3f46;--ky-ci-edge:#ffffff;--ky-ci-halo:rgba(58,63,70,.16);--ky-ci-pit:#5d636b;--ky-ci-line:#ffffff;--ky-ci-check:#15181c;--ky-ci-arrow:#f2c230;--ky-ci-arrow-edge:#3a3f46;--ky-ci-badge:#14213d;--ky-ci-badge-ink:#ffffff;--ky-ci-leader:#14213d;--ky-ci-ink:#1f2320;--ky-ci-muted:#4b535c";
|
|
5
|
+
export const CIRCUIT_DARK = "--ky-ci-ground:#10151b;--ky-ci-area:#1b232c;--ky-ci-area-edge:#334150;--ky-ci-building:#3a4756;--ky-ci-building-edge:#6f7f90;--ky-ci-track:#5d646e;--ky-ci-edge:#e6e9ed;--ky-ci-halo:rgba(230,233,237,.1);--ky-ci-pit:#7a838e;--ky-ci-line:#ffffff;--ky-ci-check:#0b0d10;--ky-ci-arrow:#f2c230;--ky-ci-arrow-edge:#10151b;--ky-ci-badge:#e9edf2;--ky-ci-badge-ink:#10151b;--ky-ci-leader:#c4ccd5;--ky-ci-ink:#eef1f4;--ky-ci-muted:#aab3bd";
|
|
6
|
+
/** The rules of a circuit's drawing, and of the turn markers and the pit building of its 3D view (`.ky-turn`). */
|
|
7
|
+
export const KYOGIJO_CIRCUIT_STYLE = `
|
|
8
|
+
:where(.ky-circuit,.ky-turn){${CIRCUIT_LIGHT}}
|
|
9
|
+
@media (prefers-color-scheme:dark){:where(html:not([data-theme=light]) .ky-circuit:not([data-theme=light]),html:not([data-theme=light]) .ky-turn:not([data-theme=light])){${CIRCUIT_DARK}}}
|
|
10
|
+
:where(html[data-theme=dark] .ky-circuit,.ky-circuit[data-theme=dark],html[data-theme=dark] .ky-turn,.ky-turn[data-theme=dark]){${CIRCUIT_DARK}}
|
|
11
|
+
:where(.ky-circuit){--ky-font:system-ui,-apple-system,"Segoe UI","Hiragino Sans","Noto Sans JP",sans-serif;display:block;width:100%;height:auto;font-family:var(--ky-font)}
|
|
12
|
+
.ky-circuit .ky-ci-ground{fill:var(--ky-ci-ground)}
|
|
13
|
+
.ky-circuit .ky-ci-area{fill:var(--ky-ci-area);stroke:var(--ky-ci-area-edge);stroke-linejoin:round}
|
|
14
|
+
.ky-circuit .ky-ci-building{fill:var(--ky-ci-building);stroke:var(--ky-ci-building-edge);stroke-linejoin:round}
|
|
15
|
+
.ky-circuit .ky-ci-halo{fill:none;stroke:var(--ky-ci-halo);stroke-linejoin:round;stroke-linecap:round}
|
|
16
|
+
.ky-circuit .ky-ci-edge{fill:none;stroke:var(--ky-ci-edge);stroke-linejoin:round;stroke-linecap:round}
|
|
17
|
+
.ky-circuit .ky-ci-track{fill:none;stroke:var(--ky-ci-track);stroke-linejoin:round;stroke-linecap:round}
|
|
18
|
+
.ky-circuit .ky-ci-pit{fill:none;stroke:var(--ky-ci-pit);stroke-linejoin:round;stroke-linecap:round}
|
|
19
|
+
.ky-circuit .ky-ci-start{stroke:var(--ky-ci-line);stroke-linecap:butt}
|
|
20
|
+
.ky-circuit .ky-ci-check-a{fill:var(--ky-ci-check)}
|
|
21
|
+
.ky-circuit .ky-ci-check-b{fill:var(--ky-ci-line)}
|
|
22
|
+
.ky-circuit .ky-ci-arrow{fill:var(--ky-ci-arrow);stroke:var(--ky-ci-arrow-edge);stroke-linejoin:round}
|
|
23
|
+
.ky-circuit .ky-ci-leader{stroke:var(--ky-ci-leader);fill:none;stroke-linecap:round}
|
|
24
|
+
.ky-circuit .ky-ci-badge,.ky-turn .u-ci-badge{fill:var(--ky-ci-badge);stroke:var(--ky-ci-ground)}
|
|
25
|
+
.ky-circuit .ky-ci-badge-n,.ky-turn .u-ci-badge-n{fill:var(--ky-ci-badge-ink);font-family:var(--ky-font,system-ui,sans-serif);font-weight:800;text-anchor:middle;font-variant-numeric:tabular-nums}
|
|
26
|
+
.ky-circuit .ky-ci-name{fill:var(--ky-ci-ink);font-family:var(--ky-font);font-weight:800}
|
|
27
|
+
.ky-circuit .ky-ci-facts{fill:var(--ky-ci-muted);font-family:var(--ky-font);font-weight:700;font-variant-numeric:tabular-nums}
|
|
28
|
+
.ky-circuit .ky-ci-north{fill:var(--ky-ci-ink);stroke:var(--ky-ci-ink);stroke-linejoin:round;stroke-linecap:round}
|
|
29
|
+
.ky-circuit .ky-ci-north-n{fill:none;stroke:var(--ky-ci-ink);stroke-linejoin:round;stroke-linecap:round}
|
|
30
|
+
.ky-circuit .ky-ci-scale{fill:none;stroke:var(--ky-ci-ink);stroke-linecap:butt}
|
|
31
|
+
.ky-circuit .ky-ci-scale-fill{fill:var(--ky-ci-ink)}
|
|
32
|
+
.ky-circuit .ky-ci-scale-text{fill:var(--ky-ci-ink);font-family:var(--ky-font);font-weight:700;font-variant-numeric:tabular-nums}
|
|
33
|
+
.ky-circuit .ky-ci-credit{fill:var(--ky-ci-muted);font-family:var(--ky-font);font-weight:600}
|
|
34
|
+
`.trim();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CircuitOptions } from "./circuits.types.ts";
|
|
2
|
+
import { type PresetTable } from "./turn.camera.ts";
|
|
3
|
+
import { type KyogijoTurn } from "./turn.dom.ts";
|
|
4
|
+
import { type TurnModel, type TurnSettings } from "./turn.model.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The four viewpoints of a circuit: straight down, the main camera high over the long side of the lap, the end-on view along it and the low corner. A lap is kilometres across and flat, so the eye is kept higher than
|
|
7
|
+
* a single surface's.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CIRCUIT_PRESETS: PresetTable;
|
|
10
|
+
/** The size of a turn's marker and the height of its post, in thousandths of the drawing's longer side (a marker, sized to be read: nothing of this size stands at a corner). */
|
|
11
|
+
export declare const CIRCUIT_MARKER: Readonly<{
|
|
12
|
+
size: 30;
|
|
13
|
+
post: 22;
|
|
14
|
+
}>;
|
|
15
|
+
/** The most planes a circuit's 3D view may have: its markers (three planes each), its pit building and nothing else. A test holds every circuit to it. */
|
|
16
|
+
export declare const CIRCUIT_PLANES_MAX = 120;
|
|
17
|
+
/**
|
|
18
|
+
* A circuit as a 3D model: its drawing as the floor, the pit building standing on it at its mapped height, and a marker with each turn's number on a post over its bend. `settings.uprights: false` is the floor alone.
|
|
19
|
+
* A `TurnModel` like any other: `turnMarkupOf` and `mountCircuitTurn` make a view of it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildCircuitTurn(options: CircuitOptions, settings?: TurnSettings): TurnModel;
|
|
22
|
+
/**
|
|
23
|
+
* Mount a circuit as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the circuit's drawing with its pit building and
|
|
24
|
+
* the turns' numbers standing up. Where the browser cannot turn it, the flat drawing is shown.
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts no-run
|
|
27
|
+
* import { mountCircuitTurn } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
|
|
28
|
+
* const view = mountCircuitTurn(document.querySelector("#lap")!, { circuit: "las-vegas" }, { preset: "corner" });
|
|
29
|
+
* view.set({ yaw: 40 });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function mountCircuitTurn(host: HTMLElement, options: CircuitOptions, settings?: TurnSettings): KyogijoTurn<CircuitOptions>;
|
|
33
|
+
/**
|
|
34
|
+
* A circuit as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it.
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { circuitTurnMarkup } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
|
|
38
|
+
* const html = circuitTurnMarkup({ circuit: "las-vegas" }, { preset: "broadcast" });
|
|
39
|
+
* console.log(html.includes('data-part="turn-17"'), html.includes("preserve-3d"));
|
|
40
|
+
* // true false
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function circuitTurnMarkup(options: CircuitOptions, settings?: TurnSettings): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// A circuit in 3D: the 3D view of `turn.ts` over the circuit's own drawing (`circuitDrawing`, its turns' badges left off the floor), with the pit building stood up at the height OpenStreetMap maps for it and each
|
|
2
|
+
// turn's number on a post over its bend, turned to face the eye so that it is read from wherever the view is. Nothing is drawn a second way, and nothing moves by itself: the camera, the controls, the pointer, the
|
|
3
|
+
// auto-turn (which waits for the reader and stops while the page is hidden or the view is out of sight) are every 3D view's. The model is a `TurnModel` of a few dozen planes; the floor is one SVG.
|
|
4
|
+
import { circuitDrawing, circuitText } from "./circuits.draw.js";
|
|
5
|
+
import { circuitOf } from "./circuits.js";
|
|
6
|
+
import { CIRCUIT_WORDS } from "./circuits.words.js";
|
|
7
|
+
import { blockPlanes, shellBlock } from "./context.js";
|
|
8
|
+
import { hash, num } from "./format.js";
|
|
9
|
+
import { FEET_PER_METRE as FT } from "./geometry.js";
|
|
10
|
+
import { fill } from "./strings.core.js";
|
|
11
|
+
import { TURN_PRESETS } from "./turn.camera.js";
|
|
12
|
+
import { mountTurn } from "./turn.dom.js";
|
|
13
|
+
import { plane, pole, thinnest } from "./turn.kit.js";
|
|
14
|
+
import { turnMarkupOf } from "./turn.markup.js";
|
|
15
|
+
import { WORLD_PX, openingView, sceneBox } from "./turn.model.js";
|
|
16
|
+
import { planesOf } from "./uprights.js";
|
|
17
|
+
/**
|
|
18
|
+
* The four viewpoints of a circuit: straight down, the main camera high over the long side of the lap, the end-on view along it and the low corner. A lap is kilometres across and flat, so the eye is kept higher than
|
|
19
|
+
* a single surface's.
|
|
20
|
+
*/
|
|
21
|
+
export const CIRCUIT_PRESETS = { overhead: TURN_PRESETS.overhead, broadcast: { yaw: 0, pitch: 58, zoom: 1 }, end: { yaw: -90, pitch: 62, zoom: 1 }, corner: { yaw: -35, pitch: 64, zoom: 1 } };
|
|
22
|
+
/** The size of a turn's marker and the height of its post, in thousandths of the drawing's longer side (a marker, sized to be read: nothing of this size stands at a corner). */
|
|
23
|
+
export const CIRCUIT_MARKER = Object.freeze({ size: 30, post: 22 });
|
|
24
|
+
/** The most planes a circuit's 3D view may have: its markers (three planes each), its pit building and nothing else. A test holds every circuit to it. */
|
|
25
|
+
export const CIRCUIT_PLANES_MAX = 120;
|
|
26
|
+
/**
|
|
27
|
+
* A circuit as a 3D model: its drawing as the floor, the pit building standing on it at its mapped height, and a marker with each turn's number on a post over its bend. `settings.uprights: false` is the floor alone.
|
|
28
|
+
* A `TurnModel` like any other: `turnMarkupOf` and `mountCircuitTurn` make a view of it.
|
|
29
|
+
*/
|
|
30
|
+
export function buildCircuitTurn(options, settings = {}) {
|
|
31
|
+
const language = options.language === "ja" ? "ja" : "en";
|
|
32
|
+
// An unknown circuit throws here, naming the keys, before anything is drawn.
|
|
33
|
+
circuitOf(options.circuit);
|
|
34
|
+
const id = settings.id ?? (typeof options.id === "string" && options.id !== "" ? `${options.id}-turn` : `ky-turn-${hash(JSON.stringify([options, settings.preset]))}`);
|
|
35
|
+
const standUp = settings.uprights !== false;
|
|
36
|
+
// The floor: the drawing, its name left to the page and its turns' numbers standing up instead (on the floor too when nothing stands).
|
|
37
|
+
const drawn = circuitDrawing({ ...options, id: `${id}-floor`, label: false, turns: !standUp });
|
|
38
|
+
const plan = drawn.plan;
|
|
39
|
+
const { x: vx, y: vy, w: vw, h: vh } = drawn.viewBox;
|
|
40
|
+
const k = drawn.k;
|
|
41
|
+
const items = [];
|
|
42
|
+
if (standUp && plan.building !== undefined) {
|
|
43
|
+
const planes = blockPlanes(shellBlock("pit", plan.building.ring, plan.building.height, undefined), "pit-");
|
|
44
|
+
const [cx, cy] = plan.building.ring.reduce((s, p) => [s[0] + p[0] / plan.building.ring.length, s[1] + p[1] / plan.building.ring.length], [0, 0]);
|
|
45
|
+
items.push({ id: "pit-building", kind: "blocks", at: [cx, cy], height: plan.building.height, planes });
|
|
46
|
+
}
|
|
47
|
+
if (standUp) {
|
|
48
|
+
// Each turn's number on a post, over the badge's place beside the bend; the marker turns to face the eye and leans back to be read square on, and lies flat seen from above.
|
|
49
|
+
const size = CIRCUIT_MARKER.size * k;
|
|
50
|
+
const top = CIRCUIT_MARKER.post * k;
|
|
51
|
+
const marks = [];
|
|
52
|
+
for (const b of drawn.badges) {
|
|
53
|
+
const [x, y] = b.at;
|
|
54
|
+
const r = size / 2;
|
|
55
|
+
const svg = `<circle class="u-ci-badge" cx="${num(r)}" cy="${num(r)}" r="${num(r * 0.94)}" stroke-width="${num(r * 0.1)}"/><text class="u-ci-badge-n" x="${num(r)}" y="${num(r + r * 0.4)}" font-size="${num(r * (b.n >= 10 ? 1.05 : 1.25))}">${b.n}</text>`;
|
|
56
|
+
marks.push({ ...plane(`turn-${b.n}`, "sign", [x - r, y, top + size], [size, 0, 0], [0, 0, -size], svg), billboard: true, lean: true });
|
|
57
|
+
marks.push(...pole(`turn-${b.n}-post`, "pole", x, y, 0, top, thinnest(vw, vh), "u-pole"));
|
|
58
|
+
}
|
|
59
|
+
items.push({ id: "turns", kind: "flags", at: [(plan.box.x0 + plan.box.x1) / 2, (plan.box.y0 + plan.box.y1) / 2], height: top + size, planes: marks });
|
|
60
|
+
}
|
|
61
|
+
const area = plan.box;
|
|
62
|
+
const uprights = { items, lift: 0, thin: thinnest(vw, vh), area };
|
|
63
|
+
const planes = planesOf(uprights);
|
|
64
|
+
const scale = WORLD_PX / Math.max(vw, vh);
|
|
65
|
+
const centre = [(area.x0 + area.x1) / 2, (area.y0 + area.y1) / 2];
|
|
66
|
+
const w = CIRCUIT_WORDS[language];
|
|
67
|
+
const standing = !standUp ? "" : plan.building === undefined ? w.standingNoBuilding : fill(w.standing, { height: num(Math.round((plan.building.height / FT) * 10) / 10) });
|
|
68
|
+
const { title, description } = circuitText(plan, language, standing);
|
|
69
|
+
const surfaceLike = { sport: "circuit", language, ...(options.theme === undefined ? {} : { theme: options.theme }) };
|
|
70
|
+
return {
|
|
71
|
+
id,
|
|
72
|
+
language,
|
|
73
|
+
options: surfaceLike,
|
|
74
|
+
title,
|
|
75
|
+
description,
|
|
76
|
+
standing,
|
|
77
|
+
floor: drawn.svg,
|
|
78
|
+
viewBox: { x: vx, y: vy, w: vw, h: vh },
|
|
79
|
+
scale,
|
|
80
|
+
uprights,
|
|
81
|
+
defs: "",
|
|
82
|
+
crowdStyle: "",
|
|
83
|
+
venue: false,
|
|
84
|
+
look: undefined,
|
|
85
|
+
planes,
|
|
86
|
+
box: sceneBox(planes, area, 0, centre, scale),
|
|
87
|
+
centre,
|
|
88
|
+
view: openingView({ preset: "broadcast", ...settings }, CIRCUIT_PRESETS),
|
|
89
|
+
turnable: true,
|
|
90
|
+
presets: CIRCUIT_PRESETS,
|
|
91
|
+
flat: circuitDrawing(options).svg,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Mount a circuit as a 3D view in a container: the same view as `mountKyogijoTurn`'s, with the controls, the pointer, the keys and the auto-turn of every 3D view, over the circuit's drawing with its pit building and
|
|
96
|
+
* the turns' numbers standing up. Where the browser cannot turn it, the flat drawing is shown.
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts no-run
|
|
99
|
+
* import { mountCircuitTurn } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
|
|
100
|
+
* const view = mountCircuitTurn(document.querySelector("#lap")!, { circuit: "las-vegas" }, { preset: "corner" });
|
|
101
|
+
* view.set({ yaw: 40 });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function mountCircuitTurn(host, options, settings = {}) {
|
|
105
|
+
return mountTurn(host, options, settings, buildCircuitTurn);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A circuit as the markup of a 3D view, still until a page's script makes it turn: pure, so a server can make it.
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { circuitTurnMarkup } from "@johnmorrisdotca/kyogijo-stadiums/circuits";
|
|
112
|
+
* const html = circuitTurnMarkup({ circuit: "las-vegas" }, { preset: "broadcast" });
|
|
113
|
+
* console.log(html.includes('data-part="turn-17"'), html.includes("preserve-3d"));
|
|
114
|
+
* // true false
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function circuitTurnMarkup(options, settings = {}) {
|
|
118
|
+
return turnMarkupOf(buildCircuitTurn(options, settings), settings);
|
|
119
|
+
}
|