// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {Widget, FlyToInterpolator, WebMercatorViewport, _GlobeViewport} from '@deck.gl/core'; import type {Viewport, WidgetPlacement, WidgetProps} from '@deck.gl/core'; import {render} from 'preact'; export type CompassWidgetProps = WidgetProps & { /** Widget positioning within the view. Default 'top-left'. */ placement?: WidgetPlacement; /** View to attach to and interact with. Required when using multiple views. */ viewId?: string | null; /** Tooltip message. */ label?: string; /** Bearing and pitch reset transition duration in ms. */ transitionDuration?: number; /** * Callback when the compass reset button is clicked. * Called for each viewport that will be reset. */ onReset?: (params: { /** The view being reset */ viewId: string; /** The new bearing value (0) */ bearing: number; /** The new pitch value (0 if bearing was already 0) */ pitch: number; }) => void; }; export class CompassWidget extends Widget { static defaultProps: Required = { ...Widget.defaultProps, id: 'compass', placement: 'top-left', viewId: null, label: 'Reset Compass', transitionDuration: 200, onReset: () => {} }; className = 'deck-widget-compass'; placement: WidgetPlacement = 'top-left'; viewports: {[id: string]: Viewport} = {}; constructor(props: CompassWidgetProps = {}) { super(props); this.setProps(this.props); } setProps(props: Partial) { this.placement = props.placement ?? this.placement; this.viewId = props.viewId ?? this.viewId; super.setProps(props); } onRenderHTML(rootElement: HTMLElement): void { const viewId = this.viewId || Object.values(this.viewports)[0]?.id; const widgetViewport = this.viewports[viewId]; const [rz, rx] = this.getRotation(widgetViewport); const ui = (
); render(ui, rootElement); } onViewportChange(viewport: Viewport) { // no need to update if viewport is the same if (!viewport.equals(this.viewports[viewport.id])) { this.viewports[viewport.id] = viewport; this.updateHTML(); } } getRotation(viewport?: Viewport) { if (viewport instanceof WebMercatorViewport) { return [-viewport.bearing, viewport.pitch]; } else if (viewport instanceof _GlobeViewport) { return [0, Math.max(-80, Math.min(80, viewport.latitude))]; } return [0, 0]; } handleCompassReset(viewport: Viewport) { const viewId = this.viewId || viewport.id; if (viewport instanceof WebMercatorViewport) { const viewState = this.getViewState(viewId); const resetPitch = this.getRotation(viewport)[0] === 0; const nextBearing = 0; const nextPitch = resetPitch ? 0 : viewport.pitch; // Call callback this.props.onReset?.({viewId, bearing: nextBearing, pitch: nextPitch}); const nextViewState = { ...viewState, bearing: nextBearing, ...(resetPitch ? {pitch: nextPitch} : {}), transitionDuration: this.props.transitionDuration, transitionInterpolator: new FlyToInterpolator() }; this.setViewState(viewId, nextViewState); } } }