/** * The three-space transform chain: data -> world -> screen. * * ``` * lon/lat --projection--> world px (fitted once) --camera--> screen px * ``` * * The projection is fitted to the container **once**, so world coordinates are * projected pixels at camera scale 1. Panning and zooming are then a pure affine * transform applied to a single SVG group, which is why interaction never * triggers reprojection. Reprojection * happens only when the projection itself, the container size, or the fitted * extent changes. * * Nothing in this module stores screen coordinates: labels, annotations and * story scenes anchor in data or world space so a resize cannot invalidate them * (section 5.1, rule 1). * * @module geo/Viewport */ import type { GeoProjection } from './Projections'; import type { Rotation } from './Versor'; import type { CameraState, LonLat, Padding, ProjectionName, ProjectionSpec, ScreenPoint, WorldBounds, WorldPoint } from '../types'; /** A d3 path generator, which doubles as a measurement tool. */ export interface GeoPathLike { (object: unknown): string | null; bounds: (object: unknown) => [[number, number], [number, number]]; centroid: (object: unknown) => [number, number]; area: (object: unknown) => number; } export declare class Viewport { width: number; height: number; projection: GeoProjection | null; projectionName: string; path: GeoPathLike | null; camera: CameraState; /** World-space bounds of the fitted content. */ worldBounds: WorldBounds | null; /** * The projection's `[lambda, phi, gamma]`, mirrored here so callers have one * place to read it and so it survives the projection being rebuilt by a * resize. Unlike the camera this is *not* a view transform: changing it moves * the sphere under the projection, so every projected coordinate on the map is * stale afterwards and has to be recomputed. */ rotation: Rotation; constructor({ width, height }?: { width?: number; height?: number; }); resize(width: number, height: number): void; setProjection(spec: ProjectionName | ProjectionSpec): void; /** * Whether the projection can be spun at all: it has to rotate, and it has to * invert, because grabbing a point on the sphere is how a drag decides what to * rotate by. */ get rotatable(): boolean; /** Whether this projection is a globe, so a drag should spin it by default. */ get isGlobeView(): boolean; /** * Turn the sphere. Longitude is wrapped rather than clamped, so a globe spins * through 360 degrees and keeps going instead of stopping at the antimeridian. * * The projection's scale and translate are untouched, so world coordinates stay * in the same space and the camera survives: rotating never refits, which is * what keeps a spin from breathing in and out as different continents come into * view. */ setRotation(angles: Rotation): void; /** * Fit the projection so `object` fills the container, minus padding. * * @param object Any GeoJSON object: FeatureCollection, Feature or geometry. */ fit(object: unknown, padding?: Padding): void; /** * Correct `fitExtent`'s residual overflow. * * d3-geo's `fitExtent` measures bounds at a fixed reference scale, but geodesic * edges are adaptively resampled, so at the final (larger) scale the rendered * shape is very slightly bigger than what was measured: an edge between two * points at the same latitude bows poleward along its great circle. The result * is a fit that overflows its extent by a pixel or two and clips at the * container boundary. * * Dense real-world coastlines hide this, sparse geometry (bounding boxes, * schematic shapes, tilegrams) does not. One correction pass converges because * the residual after rescaling is second-order. * */ private _refineFit; /** * World-space bounding box of a GeoJSON object under the current projection. * */ measure(object: unknown): WorldBounds | null; /** * SVG path string for a feature, in world space (camera-independent, so it is * safe to cache for the lifetime of the projection). * */ pathFor(feature: { geometry?: unknown; } | null | undefined): string | null; /** * The SVG transform implementing the camera. Applied to one group, so panning * and zooming cost one attribute write regardless of feature count. * */ transform(): string; worldToScreen([wx, wy]: WorldPoint): ScreenPoint; screenToWorld([sx, sy]: ScreenPoint): WorldPoint; /** Null when the point is clipped away by the projection. */ project(lonLat: LonLat): WorldPoint | null; lonLatToScreen(lonLat: LonLat): ScreenPoint | null; /** Null when the projection has no inverse. */ screenToLonLat(screen: ScreenPoint): LonLat | null; /** * Spherical bounds of a GeoJSON object, in degrees. Used for `frame()` and for * diagnostics; unlike `measure()` this is projection-independent. * */ static bounds(object: unknown): [LonLat, LonLat]; static centroid(object: unknown): LonLat; /** * Camera state that frames a world-space box inside the container. * * @param bounds World-space box. */ cameraForBounds(bounds: WorldBounds, { padding, maxZoom, }?: { padding?: Padding; maxZoom?: number; }): CameraState; /** * Camera state centred on a geographic point at a given scale. * */ cameraForCenter(lonLat: LonLat, k: number): CameraState | null; /** * The geographic point currently at the centre of the viewport. * */ center(): LonLat | null; /** * Whether re-centring this projection means **turning the sphere** rather * than panning the plane. * * True for the azimuthal family, false for everything laid out flat and for * composite projections such as Albers USA, which translate their insets * internally and break under any rotation. The camera consults this to decide * whether a `center` target is a pan or a rotation: on a globe it has to be a * rotation, because no amount of screen-space panning can bring the far * hemisphere into view. */ supportsRecentre(): boolean; /** * Run `fn` with the sphere temporarily turned to `rotation`, then put it back. * * This is how a camera move works out where it is going: the destination * camera has to be measured against the sphere as it will be *after* the * move, or the move pans across the screen to chase a point the rotation was * about to bring home anyway. Nothing renders inside the callback, so the * projection's momentary state is never observable; `finally` guarantees that * stays true even if `fn` throws. */ underRotation(rotation: Rotation, fn: () => T): T; /** * The rotation that brings a place to the sub-observer point: the centre of an * azimuthal projection, and on a globe the point facing the viewer. * * Any roll the reader has applied by dragging is preserved, because a camera * move was asked to go somewhere, not to level the horizon. */ rotationFor([lon, lat]: LonLat): Rotation; /** The place currently at the sub-observer point. */ subObserver(): LonLat; } //# sourceMappingURL=Viewport.d.ts.map