import type { GeoMapBubble } from "./geo-map.bubbles.js"; /** The map camera in viewBox units: a content point `u` draws at `k * u + t`. */ export interface GeoMapCamera { /** Zoom factor. 1 is the whole world. */ k: number; tx: number; ty: number; } /** The whole world, unzoomed and unpanned: what every non-zoomable map renders. */ export declare const WORLD_CAMERA: GeoMapCamera; export declare const MAX_ZOOM_LEVEL = 4; export declare const MAX_ZOOM: number; /** One press of a zoom control, and one arrow-key zoom step. */ export declare const ZOOM_STEP = 2; /** Wheel travel that doubles the zoom. Tuned so one notch is a nudge, not a jump. */ export declare const WHEEL_PX_PER_DOUBLING = 320; export { PAN_SLOP } from "../../style/view-camera.js"; /** How far one arrow key pans, as a fraction of the visible span. */ export declare const NUDGE_FRACTION = 0.15; /** * The discrete zoom level a factor sits in. Clustering cuts at integer levels * rather than continuously, so bubbles split at crisp, deliberate moments instead * of jittering apart and back together as a user rocks the wheel over a boundary. * A malformed factor reads as the whole world rather than propagating NaN. */ export declare function geoZoomLevel(factor: number): number; /** The zoom factor a level names; the inverse of geoZoomLevel on integer levels. */ export declare function geoZoomFactor(level: number): number; /** * Hold the camera inside the world. `k` lands in [1, MAX_ZOOM]; the offsets are * held so the drawn content still covers the viewport, which at k = 1 collapses * the range to a single point and forces tx = ty = 0. That collapse is what makes * an unzoomed zoomable map render EXACTLY as today's map does. * * A non-finite field resolves to WORLD_CAMERA rather than drawing NaN, matching * how geoMapBubbles already treats malformed coordinates. */ export declare function geoMapClampCamera(camera: GeoMapCamera): GeoMapCamera; /** * Zoom to `k` about the drawn point (ax, ay), holding whatever content already sat * under that point still. This is what makes wheel zoom feel anchored: the place * under the cursor is the place that stays put. * * Solve for the content point under the anchor, then put it back: u = (ax - tx)/k, * and tx' = ax - k' * u. */ export declare function geoMapZoomAt(camera: GeoMapCamera, k: number, ax: number, ay: number): GeoMapCamera; /** * Multiply the zoom about the viewport's centre. No pixel dependency, so the zoom * controls, the keyboard and the accessibility actions all share one path with no * notion of where a pointer is. */ export declare function geoMapZoomBy(camera: GeoMapCamera, factor: number): GeoMapCamera; /** Slide the drawn content by (dx, dy) viewBox units, held inside the world. */ export declare function geoMapPanBy(camera: GeoMapCamera, dx: number, dy: number): GeoMapCamera; /** * Move the VIEW by a fraction of what it can see: dirX = 1 looks right, which * slides the content left. Scaled by 1/k so one key press covers the same share of * the screen at every zoom rather than flying across the world when zoomed in. */ export declare function geoMapNudge(camera: GeoMapCamera, dirX: number, dirY: number): GeoMapCamera; /** * The camera a key press produces, or null when the key is not ours OR would * change nothing. Returning null for a no-op is what lets an arrow key at 1x fall * through to the page instead of being silently swallowed by a map that cannot pan. */ export declare function geoMapKeyCamera(camera: GeoMapCamera, key: string): GeoMapCamera | null; /** * The zoom factor a wheel gesture asks for. Exponential in the travel, so the * zoom feels the same whether the user is at 1x or 8x, and symmetric: scrolling * back the same distance lands exactly where it started. */ export declare function geoMapWheelFactor(deltaPx: number): number; /** The zoom factor a pinch asks for: how much the fingers have spread. */ export declare function geoMapPinchFactor(startSpan: number, span: number): number; /** * Forward-map bubble CENTRES through the camera. The radius is deliberately * untouched: bubbles are drawn outside the scaled group, so they keep a constant * size on screen and the sqrt-area encoding cannot be distorted by the zoom. * * The identity at WORLD_CAMERA, which is what licenses leaving every existing * bubbleAt test alone. */ export declare function geoMapPlace(bubbles: readonly GeoMapBubble[], camera: GeoMapCamera): GeoMapBubble[]; /** True when a placed bubble's centre is still inside the viewport. */ export declare function geoMapInView(placed: GeoMapBubble): boolean; /** * The camera as the column-major six-number matrix react-native-svg accepts * identically on all three platforms: [a, b, c, d, e, f] is * `matrix(k, 0, 0, k, tx, ty)`. The numeric form is deliberate: a transform * STRING is PEG-parsed on every native update, while the array is read straight. */ export declare function geoMapMatrix(camera: GeoMapCamera): [number, number, number, number, number, number]; /** One finger, in viewBox units. */ export interface GeoMapTouch { x: number; y: number; } /** What a gesture latched when it began, so every move can be absolute. */ export interface GeoMapGesture { camera: GeoMapCamera; /** Distance between the first two fingers when this was latched; 0 for one. */ span: number; /** Centroid of the fingers when this was latched, in viewBox units. */ cx: number; cy: number; /** How many fingers were down. A change re-latches. */ touches: number; } export declare const GEO_MAP_GESTURE_IDLE: GeoMapGesture; /** * Advance a gesture. On "grant", and whenever the finger COUNT changes mid-gesture, * this latches the camera, the finger span and the centroid, and moves nothing. * Every "move" then recomputes FROM those latched values rather than accumulating: * * k' = latched.k * (span / latched.span) (one finger leaves k alone) * u = (latched.cx - latched.tx) / latched.k (the content under the centroid) * tx' = cx - k' * u (put it back under the centroid) * * Absolute, not incremental, so replaying a move ten times lands exactly where * replaying it once does and no drift can accumulate over a long gesture. It also * means two fingers moving the same direction PAN rather than zoom, because the * span is unchanged while the centroid moves, which is the exact bug that a * PanResponder gestureState dx/dy implementation ships. * * Callers must read the fingers by ARRAY POSITION within one event, never match * them by identifier across events: react-native-web normalizes identifiers with * `identifier % 20`, so two touches can collide on one id. */ export declare function geoMapGestureEvent(phase: "grant" | "move", touches: readonly GeoMapTouch[], gesture: GeoMapGesture, camera: GeoMapCamera): { gesture: GeoMapGesture; camera: GeoMapCamera; }; //# sourceMappingURL=geo-map.camera.d.ts.map