import type { Sport } from "./types.ts"; import { type Point, type Unit } from "./units.ts"; /** A value on one axis of a provider's frame and the place it falls on the surface, in feet. */ export type Landmark = readonly [frame: number, surface: number]; /** * How a frame lies on a surface: the landmarks of its x axis and of its y axis. Two landmarks make a straight, scaled * mapping; more make one that bends at each (a pitch whose penalty box is drawn at the provider's own figures, whatever * the pitch's size). Values beyond the first and last landmark continue on the nearest line. */ export interface FrameMap { x: readonly Landmark[]; y: readonly Landmark[]; /** The frame's x runs across the surface and its y along it (the NBA's `LOC_X` is across the court). The landmarks of x then give the surface's y. */ swap?: boolean; } /** The size of a surface that varies, in a unit (feet by default). `length` runs from end to end, `width` across. */ export interface SurfaceSize { length: number; width: number; unit?: Unit; } /** The unit of a frame's numbers: a length, or what a provider's own scale is. */ export type FrameUnit = Unit | "px" | "tenth-ft" | "percent" | "fraction"; /** The ends of a surface and what a frame says about them. */ export type Ends = "fixed" | "shooter" | "none"; /** A provider's coordinate frame. */ export interface Frame { /** The key it is registered under: `nhl-api`. */ name: string; /** What it is called in words: "NHL API play-by-play". */ title: string; /** The surface it is for: one of `SPORTS`, or another sport's name for a frame of a surface made elsewhere. */ sport: Sport | (string & {}); /** The unit of its numbers. */ unit: FrameUnit; /** The range of x and of y the provider uses, `[least, greatest]`. */ x: readonly [number, number]; y: readonly [number, number]; /** Where (0, 0) lies, in words: "centre ice", "the back point of home plate", "the top left corner". */ origin: string; /** Which way y grows on the page: `up` (towards the top as the surface is drawn, wide) or `down`. */ yDirection: "up" | "down"; /** A fixed frame fits one size of surface; a variable one needs the size of the surface it is placed on. */ size: "fixed" | "variable"; /** Whether the numbers belong to the surface or to the end a team is attacking. */ ends: Ends; /** The landmarks, or for a variable frame a function of the surface's size (in feet). */ landmarks: FrameMap | ((size: { length: number; width: number; }) => FrameMap); /** The name of the source this frame was read from, in docs/sources.md. */ source: string; /** What a reader should know that the other fields do not say. */ note?: string; } /** A straight mapping between two values and back. */ export interface Mapper { /** A value on the provider's axis as a place on the surface. */ forward(value: number): number; /** A place on the surface as a value on the provider's axis. */ inverse(value: number): number; } /** * A piecewise-linear mapper through landmarks: between two landmarks a straight line, beyond the ends the line of the * nearest pair. The frame values must differ and the surface values must all rise or all fall, or the mapping could not be * undone. Reuse it for a frame whose axis bends at its landmarks. * @example * ```ts * import { piecewise } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * // A pitch drawn 0 to 100 whose halfway line is at 52.5 metres of a 105-metre pitch. * const along = piecewise([[0, 0], [50, 172.2], [100, 344.5]]); * console.log(along.forward(25), along.inverse(172.2)); * // 86.1 50 * ``` */ export declare function piecewise(landmarks: readonly Landmark[]): Mapper; /** Two landmarks as a straight mapping: `span(0, 600, 0, 200)` puts 0 at 0 feet and 600 at 200 feet. */ export declare const span: (frameA: number, frameB: number, surfaceA: number, surfaceB: number) => Landmark[]; /** Every frame by its name. `registerFrame` adds to it; the providers in frames.providers.ts are in it from the start. */ export declare const FRAMES: Record; /** * Add a frame to `FRAMES` so that `toSurface`, `fromSurface` and `convert` take it by name. A name already taken is an * error unless `replace` says it is meant. Returns the frame. * @example * ```ts * import { FRAMES, registerFrame, toSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * registerFrame({ * name: "my-rink-grid", title: "A rink in tenths of a foot", sport: "nhl", unit: "tenth-ft", x: [-1000, 1000], y: [-425, 425], * origin: "centre ice", yDirection: "up", size: "fixed", ends: "fixed", source: "my own data", * landmarks: { x: [[-1000, 0], [1000, 200]], y: [[-425, 85], [425, 0]] }, * }); * console.log(toSurface([[0, 0]], { from: "my-rink-grid" })[0], Object.keys(FRAMES).includes("my-rink-grid")); * // [ 100, 42.5 ] true * ``` */ export declare function registerFrame(frame: Frame, replace?: boolean): Frame; /** The frame with a name, or an error that lists the names. */ export declare function frameByName(name: string): Frame; /** The frames of a sport, in the order they were registered. */ export declare const framesOf: (sport: string) => Frame[]; /** The end a team is attacking: the end to the `right` of the surface as it is drawn wide, or the `left`. */ export type Attacking = "left" | "right"; /** Where points are put, and in what: what every function of this file takes besides the points. */ export interface PlaceOptions { /** The surface's size when the frame is variable, or to use another size than the sport's. */ size?: SurfaceSize; /** The end the points' team is attacking, as the surface is drawn wide: `left` turns the points a half turn about the middle so that the team attacks right. */ attacking?: Attacking; /** The unit of the surface's numbers: feet when missing. Anchors and limits are in it too. */ units?: Unit; /** Landmarks of your own for an axis, in place of the frame's: the frame's value and the surface's, which is in `units`. For a venue whose markings the frame's figures do not fit. */ anchors?: { x?: readonly Landmark[]; y?: readonly Landmark[]; }; /** Keep the points on the surface (`"surface"`), or inside a box of your own, `[least, greatest]` for each of the surface's x and y, in `units`. */ limits?: "surface" | { x?: readonly [number, number]; y?: readonly [number, number]; }; } /** * Points in a provider's frame as places on the surface: feet in the drawing's own landscape frame (x along the surface from * the left, y across it from the top; a ballpark's x is across it, y the distance out from home plate, minus). Pass * `units` for another unit, `attacking: "left"` to turn points that attack the left a half turn so that they attack the * right, `anchors` for landmarks of your own and `limits` to keep them on the surface. * @example * ```ts * import { toSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * // The NHL API's centre ice is the middle of the rink; its (69, 22) is the right end-zone face-off dot. * console.log(toSurface([[0, 0], [69, 22]], { from: "nhl-api" })); * // [ [ 100, 42.5 ], [ 169, 20.5 ] ] * ``` */ export declare function toSurface(points: readonly Point[], options: PlaceOptions & { from: Frame | string; }): [number, number][]; /** * Places on the surface as points in a provider's frame: the undoing of `toSurface`, with the same options. The places are in * `units` (feet by default). * @example * ```ts * import { fromSurface } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * // The right end-zone face-off dot of an NHL rink, as the NHL API writes it. * console.log(fromSurface([[169, 20.5]], { to: "nhl-api" })); * // [ [ 69, 22 ] ] * ``` */ export declare function fromSurface(points: readonly Point[], options: PlaceOptions & { to: Frame | string; }): [number, number][]; /** What `convert` takes besides the two frames. */ export interface ConvertOptions { /** The surface's size when a frame is variable. */ size?: SurfaceSize; /** The end the points' team attacks in the first frame's numbers, and the end it is to attack in the second's. Right for both when missing. */ attacking?: Attacking; toAttacking?: Attacking; } /** * A function that turns points from one provider's frame into another's through the surface. Both frames are of one sport. * @example * ```ts * import { convert } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * // HockeyTech's canvas to the NHL API's feet: its middle, (300, 150), is centre ice. * const toNhl = convert("hockeytech", "nhl-api"); * console.log(toNhl([[300, 150], [600, 0]])); * // [ [ 0, 0 ], [ 100, 42.5 ] ] * ``` */ export declare function convert(from: Frame | string, to: Frame | string, options?: ConvertOptions): (points: readonly Point[]) => [number, number][]; /** Points converted from one frame to another at once: `convert(from, to, options)(points)`. */ export declare const convertPoints: (points: readonly Point[], from: Frame | string, to: Frame | string, options?: ConvertOptions) => [number, number][]; /** * Turn points a half turn about the middle of a surface: where they are when the same play is drawn attacking the other end. * @example * ```ts * import { flipEnds } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * console.log(flipEnds([[169, 20.5]], { length: 200, width: 85 })); * // [ [ 31, 64.5 ] ] * ``` */ export declare function flipEnds(points: readonly Point[], size: { length: number; width: number; }): [number, number][]; /** * The end a team attacks in a period, given the end it attacked in the first: hockey changes ends every period, football * after the first and the third quarters, basketball at the half (overtime keeps the half's ends). For a period a rule does * not settle, or a sport with no rule here, pass `changedAfter`: the periods after which the teams change ends. * @example * ```ts * import { attackingIn } from "@johnmorrisdotca/kyogijo-stadiums/frames"; * console.log([1, 2, 3].map((period) => attackingIn("nhl", period, "right"))); * // [ 'right', 'left', 'right' ] * ``` */ export declare function attackingIn(sport: string, period: number, first: Attacking, changedAfter?: readonly number[]): Attacking;