import React, { SyntheticEvent, type Component } from 'react'; import { StyleSheet, type ViewProps } from 'react-native'; import { Feature, GeoJsonProperties, Geometry, Point } from 'geojson'; import { toJSONString, isFunction } from '../utils'; import checkRequiredProps from '../utils/checkRequiredProps'; import { makePoint } from '../utils/geoUtils'; import { type BaseProps } from '../types/BaseProps'; import { Position } from '../types/Position'; import RNMBXPointAnnotationNativeComponent from '../specs/RNMBXPointAnnotationNativeComponent'; import NativeRNMBXPointAnnotationModule from '../specs/NativeRNMBXPointAnnotationModule'; import NativeBridgeComponent, { type RNMBEvent } from './NativeBridgeComponent'; export const NATIVE_MODULE_NAME = 'RNMBXPointAnnotation'; const styles = StyleSheet.create({ container: { alignItems: 'center', justifyContent: 'center', position: 'absolute', }, }); type FeaturePayload = Feature< Point, { screenPointX: number; screenPointY: number; } >; type Props = BaseProps & { /** * A string that uniquely identifies the annotation */ id: string; /** * The string containing the annotation’s title. Note this is required to be set if you want to see a callout appear on iOS. */ title?: string; /** * The string containing the annotation’s snippet(subtitle). Not displayed in the default callout. */ snippet?: string; /** * Manually selects/deselects annotation */ selected?: boolean; /** * Enable or disable dragging. Defaults to false. */ draggable?: boolean; /** * The center point (specified as a map coordinate) of the annotation. */ coordinate: Position; /** * Specifies the anchor being set on a particular point of the annotation. * The anchor point is specified in the continuous space [0.0, 1.0] x [0.0, 1.0], * where (0, 0) is the top-left corner of the image, and (1, 1) is the bottom-right corner. * Note this is only for custom annotations not the default pin view. * Defaults to the center of the view. */ anchor?: { /** * See anchor */ x: number; /** * See anchor */ y: number; }; /** * This callback is fired once this annotation is selected. Returns a Feature as the first param. */ onSelected?: (payload: FeaturePayload) => void; /** * This callback is fired once this annotation is deselected. */ onDeselected?: (payload: FeaturePayload) => void; /** * This callback is fired once this annotation has started being dragged. */ onDragStart?: (payload: FeaturePayload) => void; /** * This callback is fired once this annotation has stopped being dragged. */ onDragEnd?: (payload: FeaturePayload) => void; /** * This callback is fired while this annotation is being dragged. */ onDrag?: (payload: FeaturePayload) => void; /** * Expects one child, and an optional callout can be added as well */ children: React.ReactElement | [React.ReactElement, React.ReactElement]; style?: ViewProps['style']; }; /** * PointAnnotation represents a one-dimensional shape located at a single geographical coordinate. * * Consider using ShapeSource and SymbolLayer instead, if you have many points and static images, * they'll offer much better performance. * * If you need interactive views please use MarkerView because PointAnnotation will render children onto a bitmap. * Also disable any kind of animations like `fadeDuration` of `Image`. * Otherwise, the bitmap might be rendered at an unknown state of the animation. */ class PointAnnotation extends NativeBridgeComponent( React.PureComponent, NativeRNMBXPointAnnotationModule, ) { static defaultProps = { anchor: { x: 0.5, y: 0.5 }, draggable: false, }; _nativeRef: NativePointAnnotationRef | null = null; constructor(props: Props) { super(props); checkRequiredProps('PointAnnotation', props, ['id', 'coordinate']); this._onSelected = this._onSelected.bind(this); this._onDeselected = this._onDeselected.bind(this); this._onDragStart = this._onDragStart.bind(this); this._onDrag = this._onDrag.bind(this); this._onDragEnd = this._onDragEnd.bind(this); } _decodePayload( payload: GeoJSON.Feature | string, ): GeoJSON.Feature { // we check whether the payload is a string, since the strict type safety is enforced only on iOS on the new arch // on Android, on both archs, the payload is an object if (typeof payload === 'string') { return JSON.parse(payload); } else { return payload; } } _onSelected(e: SyntheticEvent>) { if (isFunction(this.props.onSelected)) { const payload = this._decodePayload(e.nativeEvent.payload); this.props.onSelected(payload); } } _onDeselected(e: SyntheticEvent>) { if (isFunction(this.props.onDeselected)) { const payload = this._decodePayload(e.nativeEvent.payload); this.props.onDeselected(payload); } } _onDragStart(e: SyntheticEvent>) { if (isFunction(this.props.onDragStart)) { const payload = this._decodePayload(e.nativeEvent.payload); this.props.onDragStart(payload); } } _onDrag(e: SyntheticEvent>) { if (isFunction(this.props.onDrag)) { const payload = this._decodePayload(e.nativeEvent.payload); this.props.onDrag(payload); } } _onDragEnd(e: SyntheticEvent>) { if (isFunction(this.props.onDragEnd)) { const payload = this._decodePayload(e.nativeEvent.payload); this.props.onDragEnd(payload); } } _getCoordinate(): string | undefined { if (!this.props.coordinate) { return undefined; } return toJSONString(makePoint(this.props.coordinate)); } /** * On v10 and pre v10 android point annotation is rendered offscreen with a canvas into an image. * To rerender the image from the current state of the view call refresh. * Call this for example from Image#onLoad. */ refresh() { this._runNativeMethod('refresh', this._nativeRef, []); } _setNativeRef(nativeRef: NativePointAnnotationRef) { this._nativeRef = nativeRef; super._runPendingNativeMethods(nativeRef); } render() { const props = { ...this.props, ref: (nativeRef: NativePointAnnotationRef) => this._setNativeRef(nativeRef), id: this.props.id, title: this.props.title, snippet: this.props.snippet, anchor: this.props.anchor, selected: this.props.selected, draggable: this.props.draggable, style: [this.props.style, styles.container], onMapboxPointAnnotationSelected: this._onSelected, onMapboxPointAnnotationDeselected: this._onDeselected, onMapboxPointAnnotationDragStart: this._onDragStart, onMapboxPointAnnotationDrag: this._onDrag, onMapboxPointAnnotationDragEnd: this._onDragEnd, coordinate: this._getCoordinate(), }; return ( // @ts-expect-error just codegen stuff {this.props.children} ); } } type NativeProps = Omit & { coordinate: string | undefined; }; type NativePointAnnotationRef = Component; export default PointAnnotation;