import type { Coordinate, Floor } from '@mappedin/mappedin-js'; import type z from 'zod'; import type { positionSchema } from './schemas'; import type { PartialDeep } from 'type-fest'; import { BlueDotAction, BlueDotStatus, BlueDotTransition } from './status/types'; import { FollowMode } from './follow/types'; import type { PositionAnchor } from './fusion/types'; export type FollowCameraOptions = { /** * @default 21 */ zoomLevel?: number; /** * @default 45 */ pitch?: number; /** * Camera bearing in degrees clockwise from North. 0 is North, 90 is East, 180 is South, 270 is West. * This option is only available in 'position-only' mode. In all other modes, the bearing will be calculated automatically. * @default undefined */ bearing?: number; /** * @default undefined */ elevation?: number; /** * @default 1000 */ duration?: number; /** * @default 'ease-in-out' */ easing?: 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'; }; export type BlueDotEventPayloads = { /** * Emitted on every animation frame while the Blue Dot model is moving. * Unlike `position-update` (which fires only on raw sensor input, typically ~1Hz), * this fires at the display refresh rate during tweened movement, making it * suitable for smooth path-tracking visualizations. */ 'dot-position-update': { position: Coordinate; }; /** * Emitted when the Blue Dot's position is updated either from the device's geolocation API or by calling {@link BlueDot.update}. * see {@link BlueDot.watchDevicePosition} for more details. */ 'position-update': { floor: Floor | undefined; heading: GeolocationPosition['coords']['heading'] | undefined; accuracy: GeolocationPosition['coords']['accuracy'] | undefined; coordinate: Coordinate; }; /** * Emitted when the device's orientation changes and the Blue Dot's heading is updated. * see {@link BlueDot.watchDeviceOrientation} for more details. */ 'device-orientation-update': { heading: GeolocationPosition['coords']['heading'] | undefined; }; /** * Emitted when the Blue Dot's status changes. */ 'status-change': { /** * The new status of the Blue Dot. */ status: BlueDotStatus; /** * The action that caused the status change. */ action: BlueDotAction; }; /** * Emitted when the Blue Dot encounters an error. */ error: GeolocationPositionError; /** * Emitted when the Blue Dot's following state changes. */ 'follow-change': { /** * Whether the Blue Dot is following the user. */ following: boolean; /** * The mode the Blue Dot is following the user in. */ mode?: FollowMode; }; /** * Emitted when the user clicks on the Blue Dot. */ click: { coordinate: Coordinate; }; /** * Emitted when the user hovers over the Blue Dot. */ hover: { coordinate: Coordinate; }; /** * Emitted when a calibration anchor is set (e.g. from VPS or {@link BlueDot.forcePosition}). */ 'anchor-set': { anchor: PositionAnchor; }; /** * Emitted when a calibration anchor's TTL expires. */ 'anchor-expired': { anchor: PositionAnchor; }; }; export type BlueDotEvents = keyof BlueDotEventPayloads; /** * Inputs a custom {@link AccuracyRingScaleStrategy} can use to size the ring. * All distances are in the units named by the field. */ export type AccuracyRingScaleContext = { /** Reported accuracy radius in real-world metres. */ reportedAccuracyMeters: number; /** Current metres-per-pixel from the camera (`mapView.getMetersPerPixel()`). */ metersPerPixel: number; /** Map container width in CSS pixels. */ viewportWidthPx: number; /** Map container height in CSS pixels. */ viewportHeightPx: number; /** Latitude of the current fix. */ latitude: number; /** Longitude of the current fix. */ longitude: number; /** Current camera zoom level. */ zoomLevel: number; }; /** * Computes the desired accuracy-ring **radius in CSS pixels** for a fix. The SDK * converts the returned pixel radius back to a world-space model scale using the * current metres-per-pixel, so the ring stays the requested on-screen size at * the moment it is applied. Runs both on each position fix and on zoom, so a * viewport-relative cap keeps holding as the user zooms. * * Not supported in React Native: functions cannot be serialized across the * WebView bridge. Use {@link AccuracyRingScalePreset} instead. */ export type AccuracyRingScaleStrategy = (context: AccuracyRingScaleContext) => number; /** Built-in accuracy-ring scaling preset names. */ export type AccuracyRingScalePreset = 'true-scale' | 'clamp-viewport'; /** A preset name or a custom strategy for sizing the accuracy ring. */ export type AccuracyRingScaling = AccuracyRingScalePreset | AccuracyRingScaleStrategy; export type BlueDotState = { /** * Whether the BlueDot core element is visible. When false, the dot is hidden but the accuracy ring * and heading cone may still render based on their own visibility settings. * @default true */ visible: boolean; /** * The radius of the BlueDot in pixels. The BlueDot will maintain this size clamped to a minimum of 0.35 metres. * @default 10 */ radius: number; /** * The color of the BlueDot core element. * @default #2266ff */ color: string; /** * The color of the BlueDot when it has timed out and gone inactive. * @default #808080 */ inactiveColor: string; /** * Options for the accuracy ring around the BlueDot. */ accuracyRing: { /** * Whether the accuracy ring is visible. * @default true */ visible: boolean; /** * The color of the accuracy ring. * @default #2266ff */ color: string; /** * The opacity of the accuracy ring. * @default 0.3 */ opacity: number; /** * How the accuracy ring's on-screen size is derived from the reported * accuracy. Pass a built-in preset name or a custom function. * * - `'true-scale'` (default): the ring radius equals the reported * accuracy in real-world metres, so it grows/shrinks with zoom. This * is the historical behaviour. * - `'clamp-viewport'`: true scale, but capped so the ring diameter * never exceeds 60% of the smaller viewport dimension. Keeps a very * coarse fix from swallowing the screen. * - A {@link AccuracyRingScaleStrategy} function for full control — * receives the reported accuracy, current metres-per-pixel, viewport * size, position, and zoom, and returns the desired ring **radius in * CSS pixels**. Web only — functions cannot cross the React Native * WebView bridge; use a preset there. * * @default 'true-scale' */ scalingStrategy: AccuracyRingScaling; }; /** * Options for the heading directional indicator. */ heading: { /** * Whether the heading cone is visible. * @default true */ visible: boolean; /** * The color of the heading cone. * @default #2266ff */ color: string; /** * The opacity of the heading cone. * @default 0.7 */ opacity: number; /** * Whether to display the heading cone when the BlueDot is inactive (timed out). * @default false */ displayWhenInactive: boolean; }; /** * The duration of the timeout in milliseconds. * If the BlueDot does not receive a position update within this time, it will grey out until a position is received. * @default 30000 */ timeout: number; /** * Whether to watch the device's position. * @default true */ watchDevicePosition: boolean; /** * Whether to log debug messages. * @default false */ debug: boolean; /** * The maximum acceptable accuracy in meters. Position updates with accuracy exceeding this value will be dropped. * @default 50 */ accuracyThreshold: number; /** * The initial state of the BlueDot when enabled. * @default 'hidden' */ initialState: 'hidden' | 'inactive'; /** * @hidden * Whether the BlueDot must remain within the map bounds. Disabling this will disable analytics as well. * @default true */ preventOutOfBounds: boolean; /** * If true, timestamp will be used to discard updates which are received out of order. * @default true */ discardStaleUpdates: boolean; /** * Whether to reserve screen space for the BlueDot in the 2D label/marker collision * system. When enabled, an invisible always-visible Marker is kept at the BlueDot * position so floating labels and markers yield instead of painting over the 3D * model (labels/markers render on a canvas above the WebGL scene). * * Labels with `rank: 'always-visible'` never yield to collision (including this * shield) and can still paint over the BlueDot. Use `low` / `medium` / `high` for * labels that should clear when co-located with the BlueDot. * * @default false */ enable2DCollisions: boolean; }; export type BlueDotUpdateState = PartialDeep; /** * Position update options for the {@link BlueDot.update} method. */ export type BlueDotPositionUpdate = { /** * Latitude to override. * Set to `'device'` to reset to the device's latitude. */ latitude?: GeolocationPosition['coords']['latitude'] | 'device' | undefined; /** * Longitude to override. * Set to `'device'` to reset to the device's longitude. */ longitude?: GeolocationPosition['coords']['longitude'] | 'device' | undefined; /** * Accuracy to override. * Set to `'device'` to reset to the device's accuracy. * Set to `undefined` to disable the accuracy ring. */ accuracy?: GeolocationPosition['coords']['accuracy'] | 'device' | undefined; /** * Heading to override. * Set to `'device'` to reset to the device's heading. * Set to `undefined` to disable the heading indicator. */ heading?: GeolocationPosition['coords']['heading'] | 'device' | undefined; /** * Floor or floorId the Blue Dot is on. * * Set to `'device'` to reset to the device's floor level. Set to `undefined` to * clear the floor so the Blue Dot is not associated with any floor. * * Floor visibility behavior depends on the map's `multiFloorView` option: * - When `multiFloorView` is disabled, the Blue Dot is only shown while its floor * is the visible floor. Changing the map to a different floor hides the Blue Dot * (its `status` becomes `'hidden'`); returning to its floor shows it again. If no * floor is set, the Blue Dot is shown on all floors. * - When `multiFloorView` is enabled (the default), the Blue Dot is always shown * regardless of the floor value. */ floorOrFloorId?: Floor | string | 'device' | undefined; /** * Timestamp of the position update in milliseconds. */ timestamp?: number; }; export type BlueDotPositionUpdateWithFloor = Omit & { floor?: Floor | 'device' | undefined; }; export type ParsedBlueDotPosition = z.infer; export type BlueDotPositionProcessor = (current: BlueDotPositionUpdateWithFloor, incoming: BlueDotPositionUpdateWithFloor) => BlueDotPositionUpdateWithFloor | undefined; export type StateTransitions = { [Transition in BlueDotTransition]?: BlueDotStatus; }; export type StateMachine = { [State in BlueDotStatus]: { actions: StateTransitions; }; }; /** * Options for the BlueDot update method. */ export type BlueDotUpdateOptions = { /** * If true, maintains the current state and skips timers and analytics for this update. * @default false */ silent?: boolean; /** * If true, animates the position change. If false, updates immediately. * @default true */ animate?: boolean; }; export type GeolocationPositionExtended = GeolocationPosition & { coords: GeolocationPosition['coords'] & { readonly floorLevel?: number | null; }; };