@johnmorrisdotca/kyogijo-stadiums 0.45.2 → 0.46.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 (326) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/NOTICE.md +9 -2
  3. package/dist/annotate.d.ts +1 -1
  4. package/dist/annotate.js +1 -1
  5. package/dist/areas.d.ts +4 -0
  6. package/dist/areas.js +33 -1
  7. package/dist/arenas-entry.d.ts +1 -0
  8. package/dist/arenas-entry.js +2 -0
  9. package/dist/athletics/describe.js +1 -1
  10. package/dist/athletics-entry.d.ts +1 -0
  11. package/dist/athletics-entry.js +2 -0
  12. package/dist/atlas-entry.d.ts +1 -0
  13. package/dist/atlas-entry.js +2 -0
  14. package/dist/ballpark.d.ts +1 -1
  15. package/dist/ballpark.js +1 -1
  16. package/dist/budo/describe.js +1 -1
  17. package/dist/budo-entry.d.ts +1 -0
  18. package/dist/budo-entry.js +2 -0
  19. package/dist/canvas-entry.d.ts +1 -0
  20. package/dist/canvas-entry.js +2 -0
  21. package/dist/charts-entry.d.ts +1 -0
  22. package/dist/charts-entry.js +2 -0
  23. package/dist/charts.athletics.js +1 -1
  24. package/dist/charts.baseball.js +1 -1
  25. package/dist/charts.curling.heat.js +1 -1
  26. package/dist/charts.curling.js +1 -1
  27. package/dist/charts.d.ts +1 -1
  28. package/dist/charts.js +1 -1
  29. package/dist/charts.network.js +1 -1
  30. package/dist/charts.pool.js +1 -1
  31. package/dist/charts.skating.js +1 -1
  32. package/dist/charts.soccer.js +1 -1
  33. package/dist/cityctx-entry.d.ts +1 -0
  34. package/dist/cityctx-entry.js +2 -0
  35. package/dist/cityctx.belmont-park.data.d.ts +3 -0
  36. package/dist/cityctx.belmont-park.data.js +1802 -0
  37. package/dist/cityctx.data.d.ts +10 -1
  38. package/dist/cityctx.data.js +11 -2
  39. package/dist/cityctx.js +19 -1
  40. package/dist/cityctx.meadowlands.data.d.ts +3 -0
  41. package/dist/cityctx.meadowlands.data.js +820 -0
  42. package/dist/cityctx.montreal-city.data.d.ts +3 -0
  43. package/dist/cityctx.montreal-city.data.js +2899 -0
  44. package/dist/cityctx.montreal-downtown.data.d.ts +3 -0
  45. package/dist/cityctx.montreal-downtown.data.js +3004 -0
  46. package/dist/cityctx.montreal-olympic.data.d.ts +3 -0
  47. package/dist/cityctx.montreal-olympic.data.js +2562 -0
  48. package/dist/cityctx.nyc-bronx.data.d.ts +3 -0
  49. package/dist/cityctx.nyc-bronx.data.js +1671 -0
  50. package/dist/cityctx.nyc-brooklyn.data.d.ts +3 -0
  51. package/dist/cityctx.nyc-brooklyn.data.js +3485 -0
  52. package/dist/cityctx.nyc-flushing.data.d.ts +3 -0
  53. package/dist/cityctx.nyc-flushing.data.js +865 -0
  54. package/dist/cityctx.nyc-midtown.data.d.ts +3 -0
  55. package/dist/cityctx.nyc-midtown.data.js +2438 -0
  56. package/dist/crowd.d.ts +22 -2
  57. package/dist/d3-entry.d.ts +1 -0
  58. package/dist/d3-entry.js +2 -0
  59. package/dist/district-entry.d.ts +2 -0
  60. package/dist/district-entry.js +4 -0
  61. package/dist/district-lean-entry.js +1 -1
  62. package/dist/district.js +2 -2
  63. package/dist/draw.js +3 -3
  64. package/dist/element-entry.d.ts +1 -0
  65. package/dist/element-entry.js +2 -0
  66. package/dist/eras-entry.d.ts +1 -0
  67. package/dist/eras-entry.js +2 -0
  68. package/dist/events.js +1 -1
  69. package/dist/flythrough-entry.d.ts +1 -0
  70. package/dist/flythrough-entry.js +2 -0
  71. package/dist/flythrough.dom.d.ts +2 -0
  72. package/dist/flythrough.dom.js +2 -2
  73. package/dist/frames-entry.d.ts +1 -0
  74. package/dist/frames-entry.js +2 -0
  75. package/dist/full.d.ts +1 -0
  76. package/dist/full.js +3 -1
  77. package/dist/geojson-entry.d.ts +1 -0
  78. package/dist/geojson-entry.js +2 -0
  79. package/dist/geometry-entry.d.ts +1 -0
  80. package/dist/geometry-entry.js +2 -0
  81. package/dist/grounds-entry.d.ts +1 -0
  82. package/dist/grounds-entry.js +2 -0
  83. package/dist/heat.js +1 -1
  84. package/dist/heat.zones.d.ts +1 -1
  85. package/dist/heat.zones.js +1 -1
  86. package/dist/index.d.ts +3 -2
  87. package/dist/index.js +3 -1
  88. package/dist/judging-entry.d.ts +1 -0
  89. package/dist/judging-entry.js +2 -0
  90. package/dist/judging.board.js +1 -1
  91. package/dist/judging.draw.js +1 -1
  92. package/dist/judging.js +1 -1
  93. package/dist/judging.text.js +1 -1
  94. package/dist/layers.env.d.ts +1 -1
  95. package/dist/layers.env.js +1 -1
  96. package/dist/layers.js +1 -1
  97. package/dist/layout.js +1 -1
  98. package/dist/layouts-entry.d.ts +1 -0
  99. package/dist/layouts-entry.js +2 -0
  100. package/dist/leagues-entry.d.ts +1 -0
  101. package/dist/leagues-entry.js +2 -0
  102. package/dist/mount-entry.d.ts +1 -0
  103. package/dist/mount-entry.js +2 -0
  104. package/dist/multi-entry.d.ts +1 -0
  105. package/dist/multi-entry.js +2 -0
  106. package/dist/multi.draw.js +1 -1
  107. package/dist/multi.js +1 -1
  108. package/dist/osm-entry.d.ts +1 -0
  109. package/dist/osm-entry.js +2 -0
  110. package/dist/outside.d.ts +2 -1
  111. package/dist/outside.js +7 -6
  112. package/dist/parks-entry.d.ts +1 -0
  113. package/dist/parks-entry.js +2 -0
  114. package/dist/plateau-entry.d.ts +1 -0
  115. package/dist/plateau-entry.js +2 -0
  116. package/dist/plot-entry.d.ts +1 -0
  117. package/dist/plot-entry.js +2 -0
  118. package/dist/png-entry.d.ts +1 -0
  119. package/dist/png-entry.js +2 -0
  120. package/dist/pool.replay.js +1 -1
  121. package/dist/pool.words.js +1 -1
  122. package/dist/ranges.js +1 -1
  123. package/dist/react-entry.d.ts +1 -0
  124. package/dist/react-entry.js +2 -0
  125. package/dist/replay-entry.d.ts +1 -0
  126. package/dist/replay-entry.js +2 -0
  127. package/dist/replay.d.ts +1 -1
  128. package/dist/replay.dom.js +1 -1
  129. package/dist/replay.js +1 -1
  130. package/dist/rules.js +1 -1
  131. package/dist/scene-entry.d.ts +1 -0
  132. package/dist/scene-entry.js +2 -0
  133. package/dist/situation.draw.js +1 -1
  134. package/dist/situation.js +1 -1
  135. package/dist/skating/describe.js +1 -1
  136. package/dist/skating-entry.d.ts +1 -0
  137. package/dist/skating-entry.js +2 -0
  138. package/dist/stadiums-entry.d.ts +1 -0
  139. package/dist/stadiums-entry.js +2 -0
  140. package/dist/stadiums.d.ts +35 -4
  141. package/dist/stadiums.data.js +60 -0
  142. package/dist/stadiums.js +31 -5
  143. package/dist/stadiums.osm.data.js +224 -2
  144. package/dist/strings.core.d.ts +1105 -0
  145. package/dist/strings.core.js +55 -0
  146. package/dist/strings.d.ts +4 -1084
  147. package/dist/strings.en.d.ts +2 -0
  148. package/dist/strings.en.js +537 -0
  149. package/dist/strings.ja.d.ts +2 -0
  150. package/dist/strings.ja.js +562 -0
  151. package/dist/strings.js +5 -1100
  152. package/dist/surfaces/venue.js +1 -1
  153. package/dist/target.js +1 -1
  154. package/dist/team.common.js +1 -1
  155. package/dist/team.formation.js +1 -1
  156. package/dist/team.lineup.js +1 -1
  157. package/dist/team.motion.js +1 -1
  158. package/dist/team.network.js +1 -1
  159. package/dist/team.shape.js +1 -1
  160. package/dist/turn-entry.d.ts +1 -0
  161. package/dist/turn-entry.js +2 -0
  162. package/dist/turn.kit.d.ts +14 -1
  163. package/dist/turn.kit.js +19 -0
  164. package/dist/turn.model.d.ts +1 -1
  165. package/dist/turn.model.js +1 -1
  166. package/dist/turn.style.js +16 -3
  167. package/dist/uprights.js +1 -1
  168. package/dist/venue-data-entry.d.ts +2 -0
  169. package/dist/venue-data-entry.js +5 -1
  170. package/dist/venue-data.d.ts +1 -1
  171. package/dist/venue-data.js +5 -2
  172. package/dist/venue.js +3 -1
  173. package/dist/venue.real.d.ts +45 -1
  174. package/dist/venue.real.js +225 -15
  175. package/dist/venues/amerant-bank-arena.data.d.ts +2 -0
  176. package/dist/venues/amerant-bank-arena.data.js +9 -0
  177. package/dist/venues/amerant-bank-arena.osm.data.d.ts +2 -0
  178. package/dist/venues/amerant-bank-arena.osm.data.js +47 -0
  179. package/dist/venues/american-airlines-center.data.d.ts +2 -0
  180. package/dist/venues/american-airlines-center.data.js +9 -0
  181. package/dist/venues/american-airlines-center.osm.data.d.ts +2 -0
  182. package/dist/venues/american-airlines-center.osm.data.js +57 -0
  183. package/dist/venues/ball-arena.data.d.ts +2 -0
  184. package/dist/venues/ball-arena.data.js +9 -0
  185. package/dist/venues/ball-arena.osm.data.d.ts +2 -0
  186. package/dist/venues/ball-arena.osm.data.js +63 -0
  187. package/dist/venues/bell-centre.data.d.ts +2 -0
  188. package/dist/venues/bell-centre.data.js +9 -0
  189. package/dist/venues/bell-centre.osm.data.d.ts +2 -0
  190. package/dist/venues/bell-centre.osm.data.js +24 -0
  191. package/dist/venues/benchmark-international-arena.data.d.ts +2 -0
  192. package/dist/venues/benchmark-international-arena.data.js +9 -0
  193. package/dist/venues/benchmark-international-arena.osm.data.d.ts +2 -0
  194. package/dist/venues/benchmark-international-arena.osm.data.js +57 -0
  195. package/dist/venues/bridgestone-arena.data.d.ts +2 -0
  196. package/dist/venues/bridgestone-arena.data.js +9 -0
  197. package/dist/venues/bridgestone-arena.osm.data.d.ts +2 -0
  198. package/dist/venues/bridgestone-arena.osm.data.js +43 -0
  199. package/dist/venues/canada-life-centre.data.d.ts +2 -0
  200. package/dist/venues/canada-life-centre.data.js +9 -0
  201. package/dist/venues/canada-life-centre.osm.data.d.ts +2 -0
  202. package/dist/venues/canada-life-centre.osm.data.js +35 -0
  203. package/dist/venues/canadian-tire-centre.data.d.ts +2 -0
  204. package/dist/venues/canadian-tire-centre.data.js +9 -0
  205. package/dist/venues/canadian-tire-centre.osm.data.d.ts +2 -0
  206. package/dist/venues/canadian-tire-centre.osm.data.js +45 -0
  207. package/dist/venues/capital-one-arena.data.d.ts +2 -0
  208. package/dist/venues/capital-one-arena.data.js +9 -0
  209. package/dist/venues/capital-one-arena.osm.data.d.ts +2 -0
  210. package/dist/venues/capital-one-arena.osm.data.js +22 -0
  211. package/dist/venues/climate-pledge-arena.data.d.ts +2 -0
  212. package/dist/venues/climate-pledge-arena.data.js +9 -0
  213. package/dist/venues/climate-pledge-arena.osm.data.d.ts +2 -0
  214. package/dist/venues/climate-pledge-arena.osm.data.js +32 -0
  215. package/dist/venues/crypto-com-arena.data.d.ts +2 -0
  216. package/dist/venues/crypto-com-arena.data.js +9 -0
  217. package/dist/venues/crypto-com-arena.osm.data.d.ts +2 -0
  218. package/dist/venues/crypto-com-arena.osm.data.js +64 -0
  219. package/dist/venues/delta-center.data.d.ts +2 -0
  220. package/dist/venues/delta-center.data.js +9 -0
  221. package/dist/venues/delta-center.osm.data.d.ts +2 -0
  222. package/dist/venues/delta-center.osm.data.js +40 -0
  223. package/dist/venues/enterprise-center.data.d.ts +2 -0
  224. package/dist/venues/enterprise-center.data.js +9 -0
  225. package/dist/venues/enterprise-center.osm.data.d.ts +2 -0
  226. package/dist/venues/enterprise-center.osm.data.js +35 -0
  227. package/dist/venues/grand-casino-arena.data.d.ts +2 -0
  228. package/dist/venues/grand-casino-arena.data.js +9 -0
  229. package/dist/venues/grand-casino-arena.osm.data.d.ts +2 -0
  230. package/dist/venues/grand-casino-arena.osm.data.js +37 -0
  231. package/dist/venues/honda-center.data.d.ts +2 -0
  232. package/dist/venues/honda-center.data.js +9 -0
  233. package/dist/venues/honda-center.osm.data.d.ts +2 -0
  234. package/dist/venues/honda-center.osm.data.js +46 -0
  235. package/dist/venues/keybank-center.data.d.ts +2 -0
  236. package/dist/venues/keybank-center.data.js +9 -0
  237. package/dist/venues/keybank-center.osm.data.d.ts +2 -0
  238. package/dist/venues/keybank-center.osm.data.js +34 -0
  239. package/dist/venues/lenovo-center.data.d.ts +2 -0
  240. package/dist/venues/lenovo-center.data.js +9 -0
  241. package/dist/venues/lenovo-center.osm.data.d.ts +2 -0
  242. package/dist/venues/lenovo-center.osm.data.js +51 -0
  243. package/dist/venues/little-caesars-arena.data.d.ts +2 -0
  244. package/dist/venues/little-caesars-arena.data.js +9 -0
  245. package/dist/venues/little-caesars-arena.osm.data.d.ts +2 -0
  246. package/dist/venues/little-caesars-arena.osm.data.js +64 -0
  247. package/dist/venues/madison-square-garden.data.d.ts +2 -0
  248. package/dist/venues/madison-square-garden.data.js +9 -0
  249. package/dist/venues/madison-square-garden.osm.data.d.ts +2 -0
  250. package/dist/venues/madison-square-garden.osm.data.js +47 -0
  251. package/dist/venues/nationwide-arena.data.d.ts +2 -0
  252. package/dist/venues/nationwide-arena.data.js +9 -0
  253. package/dist/venues/nationwide-arena.osm.data.d.ts +2 -0
  254. package/dist/venues/nationwide-arena.osm.data.js +50 -0
  255. package/dist/venues/ppg-paints-arena.data.d.ts +2 -0
  256. package/dist/venues/ppg-paints-arena.data.js +9 -0
  257. package/dist/venues/ppg-paints-arena.osm.data.d.ts +2 -0
  258. package/dist/venues/ppg-paints-arena.osm.data.js +31 -0
  259. package/dist/venues/prudential-center.data.d.ts +2 -0
  260. package/dist/venues/prudential-center.data.js +9 -0
  261. package/dist/venues/prudential-center.osm.data.d.ts +2 -0
  262. package/dist/venues/prudential-center.osm.data.js +41 -0
  263. package/dist/venues/rogers-arena.data.d.ts +2 -0
  264. package/dist/venues/rogers-arena.data.js +10 -0
  265. package/dist/venues/rogers-arena.osm.data.d.ts +2 -0
  266. package/dist/venues/rogers-arena.osm.data.js +27 -0
  267. package/dist/venues/rogers-place.data.d.ts +2 -0
  268. package/dist/venues/rogers-place.data.js +9 -0
  269. package/dist/venues/rogers-place.osm.data.d.ts +2 -0
  270. package/dist/venues/rogers-place.osm.data.js +40 -0
  271. package/dist/venues/sap-center.data.d.ts +2 -0
  272. package/dist/venues/sap-center.data.js +9 -0
  273. package/dist/venues/sap-center.osm.data.d.ts +2 -0
  274. package/dist/venues/sap-center.osm.data.js +54 -0
  275. package/dist/venues/scotiabank-arena.data.d.ts +2 -0
  276. package/dist/venues/scotiabank-arena.data.js +9 -0
  277. package/dist/venues/scotiabank-arena.osm.data.d.ts +2 -0
  278. package/dist/venues/scotiabank-arena.osm.data.js +45 -0
  279. package/dist/venues/scotiabank-saddledome.data.d.ts +2 -0
  280. package/dist/venues/scotiabank-saddledome.data.js +9 -0
  281. package/dist/venues/scotiabank-saddledome.osm.data.d.ts +2 -0
  282. package/dist/venues/scotiabank-saddledome.osm.data.js +46 -0
  283. package/dist/venues/t-mobile-arena.data.d.ts +2 -0
  284. package/dist/venues/t-mobile-arena.data.js +9 -0
  285. package/dist/venues/t-mobile-arena.osm.data.d.ts +2 -0
  286. package/dist/venues/t-mobile-arena.osm.data.js +50 -0
  287. package/dist/venues/td-garden.data.d.ts +2 -0
  288. package/dist/venues/td-garden.data.js +9 -0
  289. package/dist/venues/td-garden.osm.data.d.ts +2 -0
  290. package/dist/venues/td-garden.osm.data.js +24 -0
  291. package/dist/venues/ubs-arena.data.d.ts +2 -0
  292. package/dist/venues/ubs-arena.data.js +9 -0
  293. package/dist/venues/ubs-arena.osm.data.d.ts +2 -0
  294. package/dist/venues/ubs-arena.osm.data.js +33 -0
  295. package/dist/venues/united-center.data.d.ts +2 -0
  296. package/dist/venues/united-center.data.js +9 -0
  297. package/dist/venues/united-center.osm.data.d.ts +2 -0
  298. package/dist/venues/united-center.osm.data.js +56 -0
  299. package/dist/venues/xfinity-mobile-arena.data.d.ts +2 -0
  300. package/dist/venues/xfinity-mobile-arena.data.js +9 -0
  301. package/dist/venues/xfinity-mobile-arena.osm.data.d.ts +2 -0
  302. package/dist/venues/xfinity-mobile-arena.osm.data.js +47 -0
  303. package/dist/venues-entry.d.ts +1 -0
  304. package/dist/venues-entry.js +2 -0
  305. package/dist/venues.all.d.ts +1 -0
  306. package/dist/venues.all.js +6 -0
  307. package/dist/venues.d.ts +18 -6
  308. package/dist/venues.data.js +1 -1
  309. package/dist/venues.index.d.ts +8 -0
  310. package/dist/venues.index.js +34 -0
  311. package/dist/venues.js +45 -8
  312. package/dist/venues.osm.data.d.ts +0 -2
  313. package/dist/venues.osm.data.js +0 -2
  314. package/dist/version.d.ts +1 -1
  315. package/dist/version.js +1 -1
  316. package/dist/walk/vancouver.data.d.ts +2 -0
  317. package/dist/walk/vancouver.data.js +1291 -0
  318. package/dist/walk.d.ts +24 -1
  319. package/dist/walk.data.d.ts +2 -2
  320. package/dist/walk.data.js +13 -1293
  321. package/dist/walk.full.d.ts +1 -0
  322. package/dist/walk.full.js +5 -0
  323. package/dist/walk.index.d.ts +7 -0
  324. package/dist/walk.index.js +3 -0
  325. package/dist/walk.js +69 -7
  326. package/package.json +7 -2
@@ -4,11 +4,14 @@
4
4
  // membrane from the rim of the walls to a crown, which never opens), or no roof at all (Koshien Stadium's). All are asked for with the roof (`venue: { roof: true }`) and give way to the eye as the other walls do.
5
5
  // Pure: data in, planes out.
6
6
  import { rayAt } from "./crowd.js";
7
- import { n, shy, wall } from "./turn.kit.js";
7
+ import { bar, box, n, pole, shy, wall } from "./turn.kit.js";
8
8
  import { pointList, raked } from "./venue.geom.js";
9
+ import { edgeDistance, inside } from "./venues.geo.js";
9
10
  /** How far a lid rises from its edge towards the middle, and how far a saddle's high ends stand over its low sides, in feet (ours: the sources give a roof's kind, not its height). */
10
11
  export const LID_RISE = 22;
11
12
  export const SADDLE_RISE = 30;
13
+ /** How far a barrel vault's crown stands over its springing at the long sides, in feet (ours: the article says the roof looks like a Quonset hut, not how high it rises). */
14
+ export const BARREL_RISE = 34;
12
15
  /** The saddle's share of its rise at a point (0 at the middle of a side, 1 at the middle of an end): half a saddle's way from one to the other, as a hyperbolic paraboloid (u squared less v squared) lies over the building's box. */
13
16
  export function saddleAt(real, [x, y]) {
14
17
  const xs = real.footprint.map((p) => p[0]);
@@ -18,8 +21,108 @@ export function saddleAt(real, [x, y]) {
18
21
  const s = ((x - cx) / hx) ** 2 - ((y - cy) / hy) ** 2;
19
22
  return (1 + Math.max(-1, Math.min(1, s))) / 2;
20
23
  }
24
+ /** The barrel vault's share of its rise at a point (0 at the springing along each long side, 1 over the middle line): a round arch over the building's short axis, the quarter of a circle's rise at each step in from the side. */
25
+ export function barrelAt(real, [, y]) {
26
+ const ys = real.footprint.map((p) => p[1]);
27
+ const [cy, hy] = [(Math.min(...ys) + Math.max(...ys)) / 2, (Math.max(...ys) - Math.min(...ys)) / 2];
28
+ const v = Math.min(1, Math.abs((y - cy) / hy));
29
+ return Math.sqrt(1 - v * v);
30
+ }
21
31
  /** The height of the roof over a point of the building's edge, above the surface, in feet. */
22
- export const roofAt = (real, p) => real.edge + (real.roof === "saddle" ? SADDLE_RISE * saddleAt(real, p) : 0);
32
+ export const roofAt = (real, p) => real.edge + (real.roof === "saddle" ? SADDLE_RISE * saddleAt(real, p) : real.roof === "barrel" ? BARREL_RISE * barrelAt(real, p) : 0);
33
+ /** How near a wall must stand to a canopy's outline to be the wall under it, in feet. */
34
+ const CANOPY_REACH = 50;
35
+ /** The height of the wall of a building under canopies at a point of its rim: where a canopy is over it, the canopy's back edge, which the wall carries up to (the outside of Lumen Field's roofs is one curved face to the ground); elsewhere the rim's own height. */
36
+ export function wallAt(real, p) {
37
+ if (real.roof !== "canopy" || real.canopies === undefined)
38
+ return roofAt(real, p);
39
+ let top = roofAt(real, p);
40
+ for (const canopy of real.canopies)
41
+ if (inside(canopy.ring, p) || edgeDistance(canopy.ring, p) < CANOPY_REACH)
42
+ top = Math.max(top, canopy.low + canopy.rise);
43
+ return top;
44
+ }
45
+ /** How many rings the leaning tower's shaft is cut into, how far its foot stands wider than the shaft over it (a plinth), and the observatory's width over the shaft's top, its height and the share of it that is glass (ours). */
46
+ const TOWER_RINGS = 12;
47
+ const TOWER_PLINTH = { wide: 1.3, high: 26 };
48
+ const TOWER_CAP = { wide: 1.5, high: 40 };
49
+ /**
50
+ * A tower that leans and curves: a square shaft of horizontal sections that tapers from a broad foot on the ground to a narrow top, along a curve that runs from the foot to the top (the line the map gives of its funicular), its
51
+ * back curving up from a flatter foot to the inclination the article gives at the top; a plinth where it meets the ground, the dark slits down its sloped back, an observatory of dark and glass bands on its top and, with `cables`,
52
+ * thin lines from the observatory to the roof. Each face of a ring is a trapezoid on a plane through its corners, so that the faces meet along their edges. The foot and the top and their height are the sources'; the curve, the
53
+ * widths, the slits, the plinth and the observatory are ours.
54
+ */
55
+ export function towerPlanes(tower, z, cables = []) {
56
+ const [bx, by] = tower.base;
57
+ const [tx, ty] = tower.top;
58
+ const run = Math.hypot(tx - bx, ty - by) || 1;
59
+ const d = [(tx - bx) / run, (ty - by) / run];
60
+ const a = [-d[1], d[0]];
61
+ const [w0, w1] = tower.width;
62
+ const H = tower.height - TOWER_CAP.high;
63
+ // The curve in the plane of the lean (run along the axis, height): a quadratic from the foot to the top whose tangent at the top rises at the article's inclination.
64
+ const lean = Math.tan((tower.angle * Math.PI) / 180);
65
+ const reach = Math.min(H * 0.12, run * 0.12);
66
+ const control = [run - reach / lean, H - reach];
67
+ const at = (t) => [2 * t * (1 - t) * control[0] + t * t * run, 2 * t * (1 - t) * control[1] + t * t * H];
68
+ const wide = (t) => w1 + (w0 - w1) * (1 - t) ** 1.15;
69
+ const offsets = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
70
+ const ring = (centre, height, w, k) => [bx + d[0] * centre[0] + (a[0] * offsets[k][0] + d[0] * offsets[k][1]) * (w / 2), by + d[1] * centre[0] + (a[1] * offsets[k][0] + d[1] * offsets[k][1]) * (w / 2), z + height];
71
+ const planes = [];
72
+ const face = (id, o, u, v, svg) => {
73
+ // (A wall, not a mast: the lower tiers leave out the small parts, and a tower 165 metres high is not one.)
74
+ planes.push({ id, kind: "wall", o, u, v, svg });
75
+ };
76
+ for (let i = 0; i < TOWER_RINGS; i += 1) {
77
+ const [t0, t1] = [i / TOWER_RINGS, (i + 1) / TOWER_RINGS];
78
+ const [p0, p1] = [at(t0), at(t1)];
79
+ const [wa, wb] = [wide(t0), wide(t1)];
80
+ for (let k = 0; k < 4; k += 1) {
81
+ const o = ring([p0[0], 0], p0[1], wa, k);
82
+ const up = ring([p1[0], 0], p1[1], wb, k);
83
+ const next = ring([p0[0], 0], p0[1], wa, (k + 1) % 4);
84
+ const u = [up[0] - o[0], up[1] - o[1], up[2] - o[2]];
85
+ const v = [next[0] - o[0], next[1] - o[1], next[2] - o[2]];
86
+ const [lu, lv] = [Math.hypot(...u), Math.hypot(...v)];
87
+ const r = wb / wa;
88
+ // The sloped back (the face away from the lean) has the two long dark slits down it, a few rings clear of the foot and of the top.
89
+ const slit = (f0, f1) => `<polygon class="u-tower-slit" points="0 ${n(f0 * lv)} ${n(lu)} ${n(f0 * lv * r)} ${n(lu)} ${n(f1 * lv * r)} 0 ${n(f1 * lv)}"/>`;
90
+ const slits = k === 0 && i >= 2 && i < TOWER_RINGS - 1 ? `${slit(0.24, 0.31)}${slit(0.62, 0.69)}` : "";
91
+ face(`tower-${i}-${k}`, o, u, v, `<polygon class="u-tower" points="0 0 ${n(lu)} 0 ${n(lu)} ${n(lv * r)} 0 ${n(lv)}"/>${slits}`);
92
+ }
93
+ }
94
+ // The plinth where the foot meets the ground: a low block wider than the shaft, with its top.
95
+ {
96
+ const wp = w0 * TOWER_PLINTH.wide;
97
+ for (let k = 0; k < 4; k += 1) {
98
+ const o = ring([0, 0], 0, wp, k);
99
+ const next = ring([0, 0], 0, wp, (k + 1) % 4);
100
+ const v = [next[0] - o[0], next[1] - o[1], 0];
101
+ face(`tower-foot-${k}`, o, [0, 0, TOWER_PLINTH.high], v, `<rect class="u-tower-foot" width="${n(TOWER_PLINTH.high)}" height="${n(Math.hypot(v[0], v[1]))}"/>`);
102
+ }
103
+ const o = ring([0, 0], TOWER_PLINTH.high, wp, 0);
104
+ const [c1, c3] = [ring([0, 0], TOWER_PLINTH.high, wp, 1), ring([0, 0], TOWER_PLINTH.high, wp, 3)];
105
+ face("tower-foot-top", o, [c1[0] - o[0], c1[1] - o[1], 0], [c3[0] - o[0], c3[1] - o[1], 0], `<rect class="u-tower-foot" width="${n(wp)}" height="${n(wp)}"/>`);
106
+ }
107
+ // The observatory on the top: a block a little wider than the shaft there, its walls dark, with a band of glass round it, and a flat roof.
108
+ const [topAt, topW] = [at(1), wide(1) * TOWER_CAP.wide];
109
+ for (let k = 0; k < 4; k += 1) {
110
+ const o = ring([topAt[0], 0], topAt[1], topW, k);
111
+ const next = ring([topAt[0], 0], topAt[1], topW, (k + 1) % 4);
112
+ const v = [next[0] - o[0], next[1] - o[1], 0];
113
+ const lv = Math.hypot(v[0], v[1]);
114
+ face(`tower-cap-${k}`, o, [0, 0, TOWER_CAP.high], v, `<rect class="u-tower-cap" width="${n(TOWER_CAP.high)}" height="${n(lv)}"/><rect class="u-tower-glass" x="${n(TOWER_CAP.high * 0.3)}" width="${n(TOWER_CAP.high * 0.45)}" height="${n(lv)}"/>`);
115
+ }
116
+ {
117
+ const o = ring([topAt[0], 0], topAt[1] + TOWER_CAP.high, topW, 0);
118
+ const [c1, c3] = [ring([topAt[0], 0], topAt[1] + TOWER_CAP.high, topW, 1), ring([topAt[0], 0], topAt[1] + TOWER_CAP.high, topW, 3)];
119
+ face("tower-cap-top", o, [c1[0] - o[0], c1[1] - o[1], 0], [c3[0] - o[0], c3[1] - o[1], 0], `<rect class="u-tower-cap" width="${n(topW)}" height="${n(topW)}"/>`);
120
+ }
121
+ // The cables that held the roof, from the observatory to the roof: thin lines.
122
+ const from = ring([topAt[0], 0], topAt[1] + TOWER_CAP.high * 0.4, 0, 0);
123
+ cables.forEach((to, i) => planes.push(...bar(`tower-cable-${i}`, "mast", from, to, 1.2, "u-tower-cable")));
124
+ return planes;
125
+ }
23
126
  /** The point where a ray from `from` first meets a polygon, or `undefined`. */
24
127
  export function firstHit(poly, from, angle) {
25
128
  const [dx, dy] = [Math.cos(angle), Math.sin(angle)];
@@ -107,11 +210,16 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
107
210
  const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
108
211
  if (long < 1)
109
212
  return;
110
- const high = lift + (real.wall ?? roofAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
111
- planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : "u-shell"}" width="${n(long)}" height="${n(high)}"/>${real.podium === undefined ? "" : podiumBands(long, high, real.podium)}`), centre), fadeTo: 0.02 });
213
+ const high = lift + (real.wall ?? wallAt(real, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]));
214
+ planes.push({ ...shy(wall(`shell-${i}`, "wall", a[0], a[1], b[0], b[1], 0, high, `<rect class="${real.ivy === true ? "u-shell u-ivy" : real.brick === true ? "u-shell u-brick" : "u-shell"}" width="${n(long)}" height="${n(high)}"/>${real.podium === undefined ? "" : podiumBands(long, high, real.podium)}`), centre), fadeTo: 0.02 });
112
215
  });
113
216
  if (parts.walls && planes.length > 0)
114
217
  items.push({ id: "shell", kind: "walls", at: centre, height: Math.max(...planes.map((p) => p.o[2])), planes });
218
+ if (parts.walls && real.towers !== undefined) {
219
+ const towers = real.towers.flatMap((tower, i) => endTowerPlanes(tower, i, lift, c));
220
+ if (towers.length > 0)
221
+ items.push({ id: "endtower", kind: "walls", at: centre, height: Math.max(...real.towers.map((t) => t.height)) + lift, planes: towers });
222
+ }
115
223
  if (parts.roof && real.roof === "canopy" && real.canopies !== undefined) {
116
224
  const planes = canopyRoof(real.canopies, c, lift, steps);
117
225
  if (planes.length > 0)
@@ -119,7 +227,15 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
119
227
  return items;
120
228
  }
121
229
  if (parts.roof && real.roof === "dome" && real.dome !== undefined) {
122
- const planes = domeRoof(real.dome, lift, steps);
230
+ // The cables run from the tower to the middle of the roof and a third of the way out each side of it.
231
+ const rim = real.dome.rim;
232
+ const mid = [rim.reduce((t, p) => t + p[0], 0) / rim.length, rim.reduce((t, p) => t + p[1], 0) / rim.length];
233
+ const reach = Math.max(...rim.map((p) => Math.hypot(p[0] - mid[0], p[1] - mid[1]))) / 3;
234
+ const across = real.tower === undefined ? [0, 0] : [-(real.tower.top[1] - real.tower.base[1]), real.tower.top[0] - real.tower.base[0]];
235
+ const long = Math.hypot(across[0], across[1]) || 1;
236
+ const high = lift + real.dome.low + (real.dome.crown - real.dome.low) * (1 - 0.1);
237
+ const cables = [[mid[0], mid[1], lift + real.dome.crown], ...[-1, 1].map((side) => [mid[0] + (across[0] / long) * reach * side, mid[1] + (across[1] / long) * reach * side, high])];
238
+ const planes = [...domeRoof(real.dome, lift, steps), ...(real.tower === undefined ? [] : towerPlanes(real.tower, lift, cables))];
123
239
  if (planes.length > 0)
124
240
  items.push({ id: "roof", kind: "roof", at: centre, height: heightOf(planes), planes });
125
241
  return items;
@@ -142,7 +258,7 @@ export function realBuilding(real, top, centre, lift, steps, open = false, parts
142
258
  const angle = (2 * Math.PI * k) / steps;
143
259
  return crown === undefined ? rayAt(top.hole, c[0], c[1], angle) : firstHit(crown.opening, c, angle) ?? rayAt(top.hole, c[0], c[1], angle);
144
260
  });
145
- const rise = real.roof === "saddle" || real.roof === "cable" ? 0 : LID_RISE;
261
+ const rise = real.roof === "saddle" || real.roof === "barrel" || real.roof === "cable" ? 0 : LID_RISE;
146
262
  const zOf = (p, inward) => lift + roofAt(real, p) + (inward ? (crown === undefined ? rise : crown.rise) : 0);
147
263
  for (let k = 0; k < steps; k += 1) {
148
264
  const [a, b] = [rim[k], rim[(k + 1) % steps]];
@@ -228,7 +344,7 @@ function span(poly, from, angle) {
228
344
  * A piece of roof between two rays: the quad `[inner a, inner b, outer b, outer a]` at the heights given, as a plane that gives way to the eye. A quad whose edges are not parallel is not flat at those heights, so
229
345
  * with `exact` it is two triangles, each a plane through its corners.
230
346
  */
231
- function piece(id, quad, heights, c, cables, exact = false) {
347
+ function piece(id, quad, heights, c, cables, exact = false, cls = "u-roof") {
232
348
  const [ai, bi, b, a] = quad;
233
349
  const [ex, ey] = [b[0] - a[0], b[1] - a[1]];
234
350
  const el = Math.hypot(ex, ey);
@@ -238,7 +354,7 @@ function piece(id, quad, heights, c, cables, exact = false) {
238
354
  const slope = slopeThrough(poly.map((p, i) => [p[0], p[1], hs[i]]));
239
355
  const mid = [poly.reduce((s, p) => s + p[0], 0) / poly.length, poly.reduce((s, p) => s + p[1], 0) / poly.length];
240
356
  const away = Math.hypot(mid[0] - c[0], mid[1] - c[1]) || 1;
241
- const paint = (corners) => `<polygon class="u-roof" points="${pointList(corners)}"/>${cable ? `<path class="u-cable" d="M${n(corners[0][0])} ${n(corners[0][1])}L${n(corners[3][0])} ${n(corners[3][1])}"/>` : ""}`;
357
+ const paint = (corners) => `<polygon class="${cls}" points="${pointList(corners)}"/>${cable ? `<path class="u-cable" d="M${n(corners[0][0])} ${n(corners[0][1])}L${n(corners[3][0])} ${n(corners[3][1])}"/>` : ""}`;
242
358
  const plane = raked(name, "roof", poly, slope, [ex / el, ey / el], paint);
243
359
  return plane === undefined ? [] : [{ ...plane, shy: [(mid[0] - c[0]) / away, (mid[1] - c[1]) / away] }];
244
360
  };
@@ -248,24 +364,69 @@ function piece(id, quad, heights, c, cables, exact = false) {
248
364
  }
249
365
  /** How far a canopy's low edge, the field's side of it, stands under its high edge: the OpenStreetMap roof's `roof:height` is the rise, and the canopy rises to the back of the stands (ours: the sources do not say which way it slopes). */
250
366
  export const CANOPY_NOTE = "low over the field, high over the back of the stands";
367
+ /** How many strips a canopy is cut into from the field's side to the back, so that a curved roof is a curve (ours: the drawing's pieces are flat). */
368
+ export const CANOPY_STRIPS = 3;
369
+ /** From this many rays round the middle on, a canopy is cut by every other one. */
370
+ export const CANOPY_FINE = 48;
371
+ /** The share of a canopy's rise at a share of the way from the field's side to the back: a straight slope, or for OpenStreetMap's `roof:shape=round` a convex arc, a quarter of a circle, steep at the field's edge and levelling over the back of the stands. */
372
+ export function canopyRiseAt(shape, t) {
373
+ return shape === "round" ? Math.sin((Math.max(0, Math.min(1, t)) * Math.PI) / 2) : t;
374
+ }
375
+ const between = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
251
376
  /**
252
- * Canopies over some of the stands (Lumen Field's two, one each side of the field): each OpenStreetMap roof outline is cut by the rays from the middle of the field into the strips between them, the edge towards
253
- * the field low and the edge towards the back of the stands `rise` higher.
377
+ * Canopies over some of the stands (Lumen Field's two, one each side of the field): each OpenStreetMap roof outline is cut by the rays from the middle of the field into the strips between them, and each strip across, from
378
+ * the edge towards the field, low, to the edge towards the back of the stands, `rise` higher, into `CANOPY_STRIPS` pieces that follow the roof's shape: a straight slope, or a round arc where OpenStreetMap says `round`.
254
379
  */
255
380
  function canopyRoof(canopies, c, lift, steps) {
256
381
  const planes = [];
257
382
  canopies.forEach((canopy, ci) => {
258
383
  const spans = Array.from({ length: steps }, (_, k) => span(canopy.ring, c, (2 * Math.PI * k) / steps));
259
- for (let k = 0; k < steps; k += 1) {
260
- const [s, t] = [spans[k], spans[(k + 1) % steps]];
384
+ // Every other ray of a fine cut: a canopy is long and gently curved, and each ray is a piece across its three strips (the plane budget of a district counts them).
385
+ const stride = steps >= CANOPY_FINE ? 2 : 1;
386
+ for (let k = 0; k < steps; k += stride) {
387
+ const [s, t] = [spans[k], spans[(k + stride) % steps]];
261
388
  if (s === undefined || t === undefined)
262
389
  continue;
263
- const [lo, hi] = [lift + canopy.low, lift + canopy.low + canopy.rise];
264
- planes.push(...piece(`roof-c${ci}-${k}`, [s[0], t[0], t[1], s[1]], [lo, lo, hi, hi], c, false, true));
390
+ for (let j = 0; j < CANOPY_STRIPS; j += 1) {
391
+ const [u0, u1] = [j / CANOPY_STRIPS, (j + 1) / CANOPY_STRIPS];
392
+ const [z0, z1] = [lift + canopy.low + canopy.rise * canopyRiseAt(canopy.shape, u0), lift + canopy.low + canopy.rise * canopyRiseAt(canopy.shape, u1)];
393
+ planes.push(...piece(`roof-c${ci}-${k}-${j}`, [between(s[0], s[1], u0), between(t[0], t[1], u0), between(t[0], t[1], u1), between(s[0], s[1], u1)], [z0, z0, z1, z1], c, false, false, "u-roof u-canopy"));
394
+ }
265
395
  }
266
396
  });
267
397
  return planes;
268
398
  }
399
+ /** How wide the scoreboard on a tower's face is and how tall, as shares of the face, and how far it stands out from it, in feet (ours: the article says the scoreboard is vertical, not how large). */
400
+ export const TOWER_BOARD = { wide: 0.62, tall: 0.46, from: 0.55, out: 0.4 };
401
+ /**
402
+ * A tower standing apart from the bowl: a box on its outline, four faces and a flat top, that gives way to the eye as a wall does (a tall tower between the eye and the field is cut away, whole from above), with the scoreboard
403
+ * on the face towards the field, a dark panel high on it and divided into its rows of lamps.
404
+ */
405
+ export function endTowerPlanes(tower, index, lift, centre) {
406
+ const xs = tower.ring.map((p) => p[0]);
407
+ const ys = tower.ring.map((p) => p[1]);
408
+ const [x0, x1, y0, y1] = [Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)];
409
+ const top = lift + tower.height;
410
+ const parts = box(`endtower-${index}`, "wall", { x0, y0, x1, y1 }, top, "u-endtower");
411
+ const planes = parts.map((p) => (p.id.endsWith("-top") ? p : { ...shy(p, centre), fadeTo: 0.25 }));
412
+ // The scoreboard: on the face that looks at the field, a little proud of it.
413
+ const [fx, fy] = tower.face;
414
+ const [w, h] = fx !== 0 ? [y1 - y0, top] : [x1 - x0, top];
415
+ const [bw, bh] = [w * TOWER_BOARD.wide, h * TOWER_BOARD.tall];
416
+ const zTop = top * TOWER_BOARD.from + bh;
417
+ const svg = `<rect class="u-endtower-board" width="${n(bw)}" height="${n(bh)}"/><path class="u-endtower-rows" d="${Array.from({ length: 9 }, (_, k) => `M0 ${n(((k + 1) * bh) / 10)}H${n(bw)}`).join("")}"/>`;
418
+ if (fx !== 0) {
419
+ const x = fx > 0 ? x1 + TOWER_BOARD.out : x0 - TOWER_BOARD.out;
420
+ const mid = (y0 + y1) / 2;
421
+ planes.push({ ...shy(wall(`endtower-${index}-board`, "wall", x, mid - bw / 2, x, mid + bw / 2, zTop - bh, zTop, svg), centre), fadeTo: 0.25 });
422
+ }
423
+ else {
424
+ const y = fy > 0 ? y1 + TOWER_BOARD.out : y0 - TOWER_BOARD.out;
425
+ const mid = (x0 + x1) / 2;
426
+ planes.push({ ...shy(wall(`endtower-${index}-board`, "wall", mid - bw / 2, y, mid + bw / 2, y, zTop - bh, zTop, svg), centre), fadeTo: 0.25 });
427
+ }
428
+ return planes;
429
+ }
269
430
  /** How many rings of pieces an air-supported roof is drawn in, from the rim in to the crown (ours: the roof is smooth, the drawing is faceted). */
270
431
  export const DOME_RINGS = 4;
271
432
  /**
@@ -315,7 +476,7 @@ const PANEL_THICK = 7;
315
476
  * building they park on, as slabs at the rim's height.
316
477
  */
317
478
  function panelsRoof(panels, c, lift, steps, open) {
318
- const planes = [];
479
+ const planes = [...panelRails(panels, c, lift)];
319
480
  if (!open) {
320
481
  const rim = Array.from({ length: steps }, (_, k) => firstHit(panels.cover, c, (2 * Math.PI * k) / steps) ?? c);
321
482
  const inner = rim.map((p) => [c[0] + (p[0] - c[0]) * PANEL_CROWN, c[1] + (p[1] - c[1]) * PANEL_CROWN]);
@@ -344,6 +505,55 @@ function panelsRoof(panels, c, lift, steps, open) {
344
505
  });
345
506
  return planes;
346
507
  }
508
+ /** How wide a rail girder is, how thick, how wide a column under it is, and how many columns stand under each rail, in feet (ours: the article says the roof runs on rails on the north and the south side, not what holds them up). */
509
+ export const RAIL = { wide: 12, thick: 16, column: 15, columns: 4, inset: 24 };
510
+ /** A prism over a four-cornered outline, from `z0` to `z1`: its sides and its top, in the roof's pod tone (a dark steel). */
511
+ function prism(id, ring, z0, z1, c) {
512
+ const planes = [];
513
+ ring.forEach((a, i) => {
514
+ const b = ring[(i + 1) % ring.length];
515
+ const long = Math.hypot(b[0] - a[0], b[1] - a[1]);
516
+ if (long < 0.5)
517
+ return;
518
+ planes.push({ ...shy(wall(`${id}-${i}`, "roof", a[0], a[1], b[0], b[1], z0, z1, `<rect class="u-rail" width="${n(long)}" height="${n(z1 - z0)}"/>`), c), fadeTo: 0.3 });
519
+ });
520
+ const top = raked(`${id}-top`, "roof", [...ring], { x0: ring[0][0], y0: ring[0][1], z0: z1, gx: 0, gy: 0 }, [1, 0], (corners) => `<polygon class="u-rail" points="${pointList(corners)}"/>`);
521
+ if (top !== undefined)
522
+ planes.push(top);
523
+ return planes;
524
+ }
525
+ /**
526
+ * The rails a retractable ballpark roof runs on, along its north and south sides (the article: 128 wheels on rails there), as two girders at the roof's lowest height, each on a row of columns from the ground, and running
527
+ * on over the strip where the sections rest. The rails run the way the sections park, from the cover's middle to where they rest; a girder lies a little inside each side of the cover.
528
+ */
529
+ export function panelRails(panels, c, lift) {
530
+ const mean = (ring) => [ring.reduce((s, p) => s + p[0], 0) / ring.length, ring.reduce((s, p) => s + p[1], 0) / ring.length];
531
+ const park = mean(panels.parked);
532
+ const length = Math.hypot(park[0] - c[0], park[1] - c[1]);
533
+ if (length < 1)
534
+ return [];
535
+ const e = [(park[0] - c[0]) / length, (park[1] - c[1]) / length];
536
+ const m = [-e[1], e[0]];
537
+ const along = (p) => (p[0] - c[0]) * e[0] + (p[1] - c[1]) * e[1];
538
+ const across = (p) => (p[0] - c[0]) * m[0] + (p[1] - c[1]) * m[1];
539
+ const [a0, a1] = [Math.min(...panels.cover.map(along)), Math.max(...panels.parked.map(along), ...panels.cover.map(along))];
540
+ const [m0, m1] = [Math.min(...panels.cover.map(across)) + RAIL.inset, Math.max(...panels.cover.map(across)) - RAIL.inset];
541
+ const at = (u, v) => [c[0] + e[0] * u + m[0] * v, c[1] + e[1] * u + m[1] * v];
542
+ const planes = [];
543
+ const z = lift + panels.bottom;
544
+ [m0, m1].forEach((v, side) => {
545
+ const w = RAIL.wide / 2;
546
+ planes.push(...prism(`rail-${side}`, [at(a0, v - w), at(a1, v - w), at(a1, v + w), at(a0, v + w)], z - RAIL.thick, z, c));
547
+ for (let k = 0; k < RAIL.columns; k += 1) {
548
+ const u = a0 + ((k + 0.5) * (a1 - a0)) / RAIL.columns;
549
+ const h = RAIL.column / 2;
550
+ // A column is two strips crossed, as a post is (`pole`): seen from every side with two planes and not five.
551
+ const [x, y] = at(u, v);
552
+ planes.push(...pole(`rail-${side}-leg-${k}`, "roof", x, y, lift, z - RAIL.thick, h * 2, "u-rail"));
553
+ }
554
+ });
555
+ return planes;
556
+ }
347
557
  /** The pod in the middle of a cable roof's opening, as a share of the opening's size, and how thick it is, in feet (ours: the article says the fabric gathers into a pod above the video board, not how large it is). */
348
558
  export const POD = { share: 0.24, thick: 9 };
349
559
  /**
@@ -0,0 +1,2 @@
1
+ import type { RealVenueData } from "../venues.ts";
2
+ export declare const VENUE: RealVenueData;
@@ -0,0 +1,9 @@
1
+ export const VENUE = {
2
+ "id": "amerant-bank-arena",
3
+ "wikidata": "Q795526",
4
+ "division": "atlantic",
5
+ "en": { "title": "Amerant Bank Arena", "revision": 1379821144 },
6
+ "seats": { "hockey": 19250, "source": "wikipedia", "wikidata": 20737, "read": "the infobox's figure for ice hockey, 19,250 (at least 20,000 with standing room); Wikidata's one capacity (20,737) has no sport and is the basketball one" },
7
+ "tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn; the article speaks of 70 suites and 2,623 club seats, which are not seating bowls" },
8
+ "roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { VenueOsm } from "../venues.ts";
2
+ export declare const OSM: VenueOsm;
@@ -0,0 +1,47 @@
1
+ export const OSM = {
2
+ "source": "way/43903801",
3
+ "read": "2026-10-20",
4
+ "base": "2026-10-20T23:14:51Z",
5
+ "licence": "ODbL-1.0",
6
+ "attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
7
+ "axis": { "heading": 90, "basis": "none", "strength": 1.16, "elongation": 1.02, "centre": [-80.325443, 26.158421] },
8
+ "ring": [
9
+ [-80.324676, 26.158684],
10
+ [-80.324614, 26.158588],
11
+ [-80.324552, 26.158446],
12
+ [-80.324708, 26.158215],
13
+ [-80.324608, 26.158149],
14
+ [-80.324762, 26.15801],
15
+ [-80.324661, 26.157905],
16
+ [-80.325085, 26.157719],
17
+ [-80.325365, 26.157557],
18
+ [-80.3254, 26.157691],
19
+ [-80.325621, 26.157659],
20
+ [-80.325627, 26.157763],
21
+ [-80.325924, 26.157768],
22
+ [-80.326026, 26.157894],
23
+ [-80.326082, 26.157998],
24
+ [-80.326126, 26.157984],
25
+ [-80.326144, 26.158016],
26
+ [-80.32619, 26.157992],
27
+ [-80.326228, 26.15808],
28
+ [-80.326258, 26.158233],
29
+ [-80.326261, 26.158415],
30
+ [-80.326242, 26.158516],
31
+ [-80.326189, 26.158716],
32
+ [-80.326139, 26.158828],
33
+ [-80.326054, 26.158935],
34
+ [-80.326022, 26.158916],
35
+ [-80.325842, 26.159111],
36
+ [-80.325688, 26.159133],
37
+ [-80.325547, 26.159133],
38
+ [-80.325544, 26.15918],
39
+ [-80.325358, 26.159169],
40
+ [-80.325129, 26.159108],
41
+ [-80.324965, 26.159041],
42
+ [-80.324812, 26.158949],
43
+ [-80.324618, 26.158787],
44
+ [-80.324672, 26.15875],
45
+ [-80.324645, 26.158707],
46
+ ],
47
+ };
@@ -0,0 +1,2 @@
1
+ import type { RealVenueData } from "../venues.ts";
2
+ export declare const VENUE: RealVenueData;
@@ -0,0 +1,9 @@
1
+ export const VENUE = {
2
+ "id": "american-airlines-center",
3
+ "wikidata": "Q463345",
4
+ "division": "central",
5
+ "en": { "title": "American Airlines Center", "revision": 1379833787 },
6
+ "seats": { "hockey": 18532, "source": "wikipedia", "wikidata": 19200, "read": "the infobox's figure for ice hockey, 18,532 (19,323 with standing room); Wikidata's one capacity (19,200) is the basketball one" },
7
+ "tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn; the article speaks of retractable seating, which the bowl does not draw" },
8
+ "roof": { "kind": "barrel", "basis": "documented", "read": "the article says the roof looks like a Quonset hut, which is a barrel vault: drawn as a round arch over the rink's short axis, highest over its middle line and falling to the walls at the two long sides; the height of the crown (34 feet over the springing) is ours (the article gives none)" },
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { VenueOsm } from "../venues.ts";
2
+ export declare const OSM: VenueOsm;
@@ -0,0 +1,57 @@
1
+ export const OSM = {
2
+ "source": "way/25312698",
3
+ "read": "2026-10-20",
4
+ "base": "2026-10-20T21:35:07Z",
5
+ "licence": "ODbL-1.0",
6
+ "attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
7
+ "axis": { "heading": 144, "basis": "building", "strength": 2.19, "elongation": 1.31, "centre": [-96.81028, 32.79049] },
8
+ "ring": [
9
+ [-96.810234, 32.791328],
10
+ [-96.810164, 32.791245],
11
+ [-96.810182, 32.791236],
12
+ [-96.810056, 32.791086],
13
+ [-96.810012, 32.791113],
14
+ [-96.809956, 32.791046],
15
+ [-96.809969, 32.791039],
16
+ [-96.809568, 32.790561],
17
+ [-96.809555, 32.790569],
18
+ [-96.8095, 32.790504],
19
+ [-96.809545, 32.790477],
20
+ [-96.80934, 32.790256],
21
+ [-96.809439, 32.790198],
22
+ [-96.809429, 32.790171],
23
+ [-96.80958, 32.790081],
24
+ [-96.809551, 32.790046],
25
+ [-96.809959, 32.789817],
26
+ [-96.809952, 32.789809],
27
+ [-96.81003, 32.789763],
28
+ [-96.810056, 32.789794],
29
+ [-96.810209, 32.789704],
30
+ [-96.81024, 32.789718],
31
+ [-96.810339, 32.789661],
32
+ [-96.810408, 32.789744],
33
+ [-96.810392, 32.789752],
34
+ [-96.81051, 32.789892],
35
+ [-96.810556, 32.789865],
36
+ [-96.810611, 32.789932],
37
+ [-96.810599, 32.789939],
38
+ [-96.810997, 32.790421],
39
+ [-96.81101, 32.790413],
40
+ [-96.811063, 32.790477],
41
+ [-96.811021, 32.790502],
42
+ [-96.811154, 32.790659],
43
+ [-96.811168, 32.790649],
44
+ [-96.811237, 32.790731],
45
+ [-96.811139, 32.79079],
46
+ [-96.81113, 32.790776],
47
+ [-96.810963, 32.790875],
48
+ [-96.811009, 32.790929],
49
+ [-96.810986, 32.790943],
50
+ [-96.810931, 32.790975],
51
+ [-96.810924, 32.790967],
52
+ [-96.81053, 32.791213],
53
+ [-96.810484, 32.791159],
54
+ [-96.810319, 32.791257],
55
+ [-96.810331, 32.791269],
56
+ ],
57
+ };
@@ -0,0 +1,2 @@
1
+ import type { RealVenueData } from "../venues.ts";
2
+ export declare const VENUE: RealVenueData;
@@ -0,0 +1,9 @@
1
+ export const VENUE = {
2
+ "id": "ball-arena",
3
+ "wikidata": "Q795470",
4
+ "division": "central",
5
+ "en": { "title": "Ball Arena", "revision": 1379560431 },
6
+ "seats": { "hockey": 17809, "source": "wikipedia", "wikidata": 19155, "read": "the infobox's figure for hockey (given with lacrosse), 17,809; Wikidata's capacity without a sport is 19,155 and its other (17,417) is for futsal" },
7
+ "tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn" },
8
+ "roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { VenueOsm } from "../venues.ts";
2
+ export declare const OSM: VenueOsm;
@@ -0,0 +1,63 @@
1
+ export const OSM = {
2
+ "source": "way/25312645",
3
+ "read": "2026-10-20",
4
+ "base": "2026-10-20T21:33:18Z",
5
+ "licence": "ODbL-1.0",
6
+ "attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
7
+ "height": 22,
8
+ "axis": { "heading": 90, "basis": "none", "strength": 1.26, "elongation": 1.02, "centre": [-105.007573, 39.748633] },
9
+ "ring": [
10
+ [-105.008169, 39.749084],
11
+ [-105.008338, 39.748973],
12
+ [-105.008439, 39.748857],
13
+ [-105.008498, 39.748726],
14
+ [-105.008512, 39.748603],
15
+ [-105.008498, 39.748498],
16
+ [-105.008449, 39.748382],
17
+ [-105.00837, 39.748281],
18
+ [-105.008268, 39.7482],
19
+ [-105.008098, 39.748114],
20
+ [-105.008077, 39.748156],
21
+ [-105.007939, 39.748121],
22
+ [-105.00785, 39.748115],
23
+ [-105.007829, 39.748063],
24
+ [-105.007872, 39.74804],
25
+ [-105.007756, 39.747764],
26
+ [-105.007484, 39.74798],
27
+ [-105.007254, 39.748038],
28
+ [-105.007224, 39.747996],
29
+ [-105.00704, 39.74808],
30
+ [-105.006878, 39.748188],
31
+ [-105.00693, 39.748223],
32
+ [-105.00681, 39.748372],
33
+ [-105.006749, 39.748523],
34
+ [-105.006624, 39.74863],
35
+ [-105.006638, 39.748641],
36
+ [-105.006521, 39.748736],
37
+ [-105.006734, 39.748736],
38
+ [-105.006738, 39.748757],
39
+ [-105.006766, 39.748757],
40
+ [-105.006796, 39.748878],
41
+ [-105.00686, 39.748986],
42
+ [-105.00696, 39.749077],
43
+ [-105.006919, 39.749103],
44
+ [-105.007099, 39.749198],
45
+ [-105.007072, 39.74924],
46
+ [-105.006874, 39.749291],
47
+ [-105.006962, 39.749349],
48
+ [-105.00719, 39.749449],
49
+ [-105.007191, 39.749467],
50
+ [-105.00745, 39.749525],
51
+ [-105.007633, 39.749535],
52
+ [-105.007613, 39.749485],
53
+ [-105.007884, 39.749454],
54
+ [-105.007869, 39.749385],
55
+ [-105.007985, 39.749351],
56
+ [-105.007956, 39.749294],
57
+ [-105.008018, 39.749277],
58
+ [-105.007993, 39.749232],
59
+ [-105.008094, 39.749221],
60
+ [-105.008075, 39.749183],
61
+ [-105.008185, 39.749115],
62
+ ],
63
+ };
@@ -0,0 +1,2 @@
1
+ import type { RealVenueData } from "../venues.ts";
2
+ export declare const VENUE: RealVenueData;
@@ -0,0 +1,9 @@
1
+ export const VENUE = {
2
+ "id": "bell-centre",
3
+ "wikidata": "Q522282",
4
+ "division": "atlantic",
5
+ "en": { "title": "Bell Centre", "revision": 1380110375 },
6
+ "seats": { "hockey": 20962, "source": "wikipedia", "wikidata": 21273, "read": "the article's seating capacity for hockey, 20,962 (the infobox's present figure, from 2025); Wikidata's one capacity (21,273) is the first figure of 1996" },
7
+ "tiers": { "count": 3, "basis": "documented", "read": "the article says the Bell Centre is arranged in a three-tier layout: the lower 100 section, the 200 section between two levels of boxes, and the upper 300 section" },
8
+ "roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise" },
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { VenueOsm } from "../venues.ts";
2
+ export declare const OSM: VenueOsm;
@@ -0,0 +1,24 @@
1
+ export const OSM = {
2
+ "source": "way/19911284",
3
+ "read": "2026-10-20",
4
+ "base": "2026-10-20T23:14:51Z",
5
+ "licence": "ODbL-1.0",
6
+ "attribution": "(c) OpenStreetMap contributors, ODbL 1.0, openstreetmap.org/copyright",
7
+ "axis": { "heading": 132, "basis": "building", "strength": 2.28, "elongation": 1.53, "centre": [-73.569281, 45.496067] },
8
+ "ring": [
9
+ [-73.570363, 45.496182],
10
+ [-73.569576, 45.496827],
11
+ [-73.569294, 45.496653],
12
+ [-73.569259, 45.496681],
13
+ [-73.568425, 45.496166],
14
+ [-73.568449, 45.496144],
15
+ [-73.568418, 45.496124],
16
+ [-73.568329, 45.496108],
17
+ [-73.5683, 45.496056],
18
+ [-73.568168, 45.495975],
19
+ [-73.56905, 45.495279],
20
+ [-73.569731, 45.49569],
21
+ [-73.570154, 45.495977],
22
+ [-73.570107, 45.496015],
23
+ ],
24
+ };
@@ -0,0 +1,2 @@
1
+ import type { RealVenueData } from "../venues.ts";
2
+ export declare const VENUE: RealVenueData;
@@ -0,0 +1,9 @@
1
+ export const VENUE = {
2
+ "id": "benchmark-international-arena",
3
+ "wikidata": "Q940199",
4
+ "division": "atlantic",
5
+ "en": { "title": "Benchmark International Arena", "revision": 1376541285 },
6
+ "seats": { "hockey": 19092, "source": "wikipedia", "wikidata": 19204, "read": "the infobox's figure for ice hockey, 19,092; Wikidata's one capacity (19,204) has no sport" },
7
+ "tiers": { "count": 2, "basis": "plausible", "read": "no source says how many seating levels there are: two bowls are drawn" },
8
+ "roof": { "kind": "fixed", "basis": "plausible", "read": "an indoor arena: a fixed roof, as nothing says otherwise; OpenStreetMap maps the building 43.47 metres high" },
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { VenueOsm } from "../venues.ts";
2
+ export declare const OSM: VenueOsm;